001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (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_COPYRIGHT; 032import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_CREATED; 033import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_EXPIRED; 034import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_MODIFIED; 035import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_DATE_RELEASED; 036import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT; 037import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_INTERNAL_RESOURCE_TYPE; 038import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_IN_NAVIGATION; 039import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_IS_FOLDER; 040import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION; 041import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT; 042import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_PERMISSIONS; 043import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_PROJECT; 044import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED; 045import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RESOURCE_NAME; 046import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_RESOURCE_TYPE; 047import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_SIZE; 048import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_STATE; 049import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_STATE_NAME; 050import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_TITLE; 051import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_TYPE_ICON; 052import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_CREATED; 053import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_LOCKED; 054import static org.opencms.ui.components.CmsResourceTableProperty.PROPERTY_USER_MODIFIED; 055 056import org.opencms.db.CmsResourceState; 057import org.opencms.file.CmsObject; 058import org.opencms.file.CmsResource; 059import org.opencms.file.CmsResourceFilter; 060import org.opencms.file.CmsVfsResourceNotFoundException; 061import org.opencms.main.CmsException; 062import org.opencms.main.CmsLog; 063import org.opencms.main.OpenCms; 064import org.opencms.ui.A_CmsUI; 065import org.opencms.ui.CmsVaadinUtils; 066import org.opencms.ui.I_CmsDialogContext; 067import org.opencms.ui.I_CmsEditPropertyContext; 068import org.opencms.ui.actions.I_CmsDefaultAction; 069import org.opencms.ui.apps.CmsFileExplorerSettings; 070import org.opencms.ui.apps.I_CmsContextProvider; 071import org.opencms.ui.contextmenu.CmsContextMenu; 072import org.opencms.ui.contextmenu.CmsResourceContextMenuBuilder; 073import org.opencms.ui.contextmenu.I_CmsContextMenuBuilder; 074import org.opencms.ui.util.I_CmsItemSorter; 075import org.opencms.util.CmsStringUtil; 076import org.opencms.util.CmsUUID; 077 078import java.io.ByteArrayOutputStream; 079import java.io.OutputStreamWriter; 080import java.nio.charset.StandardCharsets; 081import java.text.DateFormat; 082import java.text.SimpleDateFormat; 083import java.util.ArrayList; 084import java.util.Arrays; 085import java.util.Collection; 086import java.util.Collections; 087import java.util.Date; 088import java.util.HashSet; 089import java.util.LinkedHashMap; 090import java.util.List; 091import java.util.Locale; 092import java.util.Map; 093import java.util.Map.Entry; 094import java.util.Set; 095import java.util.TimeZone; 096 097import org.apache.commons.logging.Log; 098 099import com.google.common.base.Function; 100import com.google.common.collect.Lists; 101import com.vaadin.event.FieldEvents.BlurEvent; 102import com.vaadin.event.FieldEvents.BlurListener; 103import com.vaadin.event.ShortcutAction.KeyCode; 104import com.vaadin.event.ShortcutListener; 105import com.vaadin.shared.MouseEventDetails.MouseButton; 106import com.vaadin.ui.Component; 107import com.vaadin.ui.themes.ValoTheme; 108import com.vaadin.v7.data.Container; 109import com.vaadin.v7.data.Container.Filter; 110import com.vaadin.v7.data.Item; 111import com.vaadin.v7.data.Property.ValueChangeEvent; 112import com.vaadin.v7.data.Property.ValueChangeListener; 113import com.vaadin.v7.data.util.DefaultItemSorter; 114import com.vaadin.v7.data.util.IndexedContainer; 115import com.vaadin.v7.data.util.filter.Or; 116import com.vaadin.v7.data.util.filter.SimpleStringFilter; 117import com.vaadin.v7.event.ItemClickEvent; 118import com.vaadin.v7.event.ItemClickEvent.ItemClickListener; 119import com.vaadin.v7.ui.AbstractTextField.TextChangeEventMode; 120import com.vaadin.v7.ui.DefaultFieldFactory; 121import com.vaadin.v7.ui.Field; 122import com.vaadin.v7.ui.Table; 123import com.vaadin.v7.ui.Table.TableDragMode; 124import com.vaadin.v7.ui.TextField; 125 126import au.com.bytecode.opencsv.CSVWriter; 127 128/** 129 * Table for displaying resources.<p> 130 */ 131public class CmsFileTable extends CmsResourceTable { 132 133 /** 134 * File edit handler.<p> 135 */ 136 public class FileEditHandler implements BlurListener { 137 138 /** The serial version id. */ 139 private static final long serialVersionUID = -2286815522247807054L; 140 141 /** 142 * @see com.vaadin.event.FieldEvents.BlurListener#blur(com.vaadin.event.FieldEvents.BlurEvent) 143 */ 144 public void blur(BlurEvent event) { 145 146 stopEdit(); 147 } 148 } 149 150 /** 151 * Field factory to enable inline editing of individual file properties.<p> 152 */ 153 public class FileFieldFactory extends DefaultFieldFactory { 154 155 /** The serial version id. */ 156 private static final long serialVersionUID = 3079590603587933576L; 157 158 /** 159 * @see com.vaadin.ui.DefaultFieldFactory#createField(com.vaadin.v7.data.Container, java.lang.Object, java.lang.Object, com.vaadin.ui.Component) 160 */ 161 @Override 162 public Field<?> createField(Container container, Object itemId, Object propertyId, Component uiContext) { 163 164 Field<?> result = null; 165 if (itemId.equals(getEditItemId().toString()) && isEditProperty((CmsResourceTableProperty)propertyId)) { 166 result = super.createField(container, itemId, propertyId, uiContext); 167 result.addStyleName(OpenCmsTheme.INLINE_TEXTFIELD); 168 result.addValidator(m_editHandler); 169 if (result instanceof TextField) { 170 ((TextField)result).setComponentError(null); 171 ((TextField)result).addShortcutListener(new ShortcutListener("Cancel edit", KeyCode.ESCAPE, null) { 172 173 private static final long serialVersionUID = 1L; 174 175 @Override 176 public void handleAction(Object sender, Object target) { 177 178 cancelEdit(); 179 } 180 }); 181 ((TextField)result).addShortcutListener(new ShortcutListener("Save", KeyCode.ENTER, null) { 182 183 private static final long serialVersionUID = 1L; 184 185 @Override 186 public void handleAction(Object sender, Object target) { 187 188 stopEdit(); 189 } 190 }); 191 ((TextField)result).addBlurListener(m_fileEditHandler); 192 ((TextField)result).setTextChangeEventMode(TextChangeEventMode.LAZY); 193 ((TextField)result).addTextChangeListener(m_editHandler); 194 } 195 result.focus(); 196 } 197 return result; 198 } 199 } 200 201 /** 202 * Extends the default sorting to differentiate between files and folder when sorting by name.<p> 203 * Also allows sorting by navPos property for the Resource icon column.<p> 204 */ 205 public static class FileSorter extends DefaultItemSorter implements I_CmsItemSorter { 206 207 /** The serial version id. */ 208 private static final long serialVersionUID = 1L; 209 210 /** 211 * @see org.opencms.ui.util.I_CmsItemSorter#getSortableContainerPropertyIds(com.vaadin.v7.data.Container) 212 */ 213 public Collection<?> getSortableContainerPropertyIds(Container container) { 214 215 Set<Object> result = new HashSet<Object>(); 216 for (Object propId : container.getContainerPropertyIds()) { 217 Class<?> propertyType = container.getType(propId); 218 if (Comparable.class.isAssignableFrom(propertyType) 219 || propertyType.isPrimitive() 220 || (propId.equals(CmsResourceTableProperty.PROPERTY_TYPE_ICON) 221 && container.getContainerPropertyIds().contains( 222 CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION))) { 223 result.add(propId); 224 } 225 } 226 return result; 227 } 228 229 /** 230 * @see com.vaadin.v7.data.util.DefaultItemSorter#compareProperty(java.lang.Object, boolean, com.vaadin.v7.data.Item, com.vaadin.v7.data.Item) 231 */ 232 @Override 233 protected int compareProperty(Object propertyId, boolean sortDirection, Item item1, Item item2) { 234 235 //@formatter:off 236 if (CmsResourceTableProperty.PROPERTY_RESOURCE_NAME.equals(propertyId)) { 237 Boolean isFolder1 = (Boolean)item1.getItemProperty( 238 CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue(); 239 Boolean isFolder2 = (Boolean)item2.getItemProperty( 240 CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue(); 241 if (!isFolder1.equals(isFolder2)) { 242 int result = isFolder1.booleanValue() ? -1 : 1; 243 if (!sortDirection) { 244 result = result * (-1); 245 } 246 return result; 247 } 248 } else if ((CmsResourceTableProperty.PROPERTY_TYPE_ICON.equals(propertyId) 249 || CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT.equals(propertyId)) 250 && (item1.getItemProperty(CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION) != null)) { 251 int result; 252 Float pos1 = (Float)item1.getItemProperty( 253 CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION).getValue(); 254 Float pos2 = (Float)item2.getItemProperty( 255 CmsResourceTableProperty.PROPERTY_NAVIGATION_POSITION).getValue(); 256 if (pos1 == null) { 257 result = pos2 == null 258 ? compareProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, true, item1, item2) 259 : 1; 260 } else { 261 result = pos2 == null ? -1 : Float.compare(pos1.floatValue(), pos2.floatValue()); 262 } 263 if (!sortDirection) { 264 result = result * (-1); 265 } 266 return result; 267 } else if (((CmsResourceTableProperty)propertyId).getColumnType().equals(String.class)) { 268 String value1 = (String)item1.getItemProperty(propertyId).getValue(); 269 String value2 = (String)item2.getItemProperty(propertyId).getValue(); 270 // Java collators obtained by java.text.Collator.getInstance(...) ignore spaces, and we don't want to ignore them, so we use 271 // ICU collators instead 272 com.ibm.icu.text.Collator collator = com.ibm.icu.text.Collator.getInstance( 273 com.ibm.icu.util.ULocale.ROOT); 274 int result = collator.compare(value1, value2); 275 if (!sortDirection) { 276 result = -result; 277 } 278 return result; 279 } 280 return super.compareProperty(propertyId, sortDirection, item1, item2); 281 //@formatter:on 282 } 283 } 284 285 /** 286 * Handles folder selects in the file table.<p> 287 */ 288 public interface I_FolderSelectHandler { 289 290 /** 291 * Called when the folder name is left clicked.<p> 292 * 293 * @param folderId the selected folder id 294 */ 295 void onFolderSelect(CmsUUID folderId); 296 } 297 298 /** The default file table columns. */ 299 public static final Map<CmsResourceTableProperty, Integer> DEFAULT_TABLE_PROPERTIES; 300 301 /** The logger instance for this class. */ 302 static final Log LOG = CmsLog.getLog(CmsFileTable.class); 303 304 /** The serial version id. */ 305 private static final long serialVersionUID = 5460048685141699277L; 306 307 static { 308 Map<CmsResourceTableProperty, Integer> defaultProps = new LinkedHashMap<CmsResourceTableProperty, Integer>(); 309 defaultProps.put(PROPERTY_TYPE_ICON, Integer.valueOf(0)); 310 defaultProps.put(PROPERTY_PROJECT, Integer.valueOf(COLLAPSED)); 311 defaultProps.put(PROPERTY_RESOURCE_NAME, Integer.valueOf(0)); 312 defaultProps.put(PROPERTY_TITLE, Integer.valueOf(0)); 313 defaultProps.put(PROPERTY_NAVIGATION_TEXT, Integer.valueOf(COLLAPSED)); 314 defaultProps.put(PROPERTY_NAVIGATION_POSITION, Integer.valueOf(INVISIBLE)); 315 defaultProps.put(PROPERTY_IN_NAVIGATION, Integer.valueOf(INVISIBLE)); 316 defaultProps.put(PROPERTY_COPYRIGHT, Integer.valueOf(COLLAPSED)); 317 defaultProps.put(PROPERTY_CACHE, Integer.valueOf(COLLAPSED)); 318 defaultProps.put(PROPERTY_RESOURCE_TYPE, Integer.valueOf(0)); 319 defaultProps.put(PROPERTY_INTERNAL_RESOURCE_TYPE, Integer.valueOf(COLLAPSED)); 320 defaultProps.put(PROPERTY_SIZE, Integer.valueOf(0)); 321 defaultProps.put(PROPERTY_PERMISSIONS, Integer.valueOf(COLLAPSED)); 322 defaultProps.put(PROPERTY_DATE_MODIFIED, Integer.valueOf(0)); 323 defaultProps.put(PROPERTY_USER_MODIFIED, Integer.valueOf(COLLAPSED)); 324 defaultProps.put(PROPERTY_DATE_CREATED, Integer.valueOf(COLLAPSED)); 325 defaultProps.put(PROPERTY_USER_CREATED, Integer.valueOf(COLLAPSED)); 326 defaultProps.put(PROPERTY_DATE_RELEASED, Integer.valueOf(0)); 327 defaultProps.put(PROPERTY_DATE_EXPIRED, Integer.valueOf(0)); 328 defaultProps.put(PROPERTY_STATE_NAME, Integer.valueOf(0)); 329 defaultProps.put(PROPERTY_USER_LOCKED, Integer.valueOf(0)); 330 defaultProps.put(PROPERTY_IS_FOLDER, Integer.valueOf(INVISIBLE)); 331 defaultProps.put(PROPERTY_STATE, Integer.valueOf(INVISIBLE)); 332 defaultProps.put(PROPERTY_INSIDE_PROJECT, Integer.valueOf(INVISIBLE)); 333 defaultProps.put(PROPERTY_RELEASED_NOT_EXPIRED, Integer.valueOf(INVISIBLE)); 334 DEFAULT_TABLE_PROPERTIES = Collections.unmodifiableMap(defaultProps); 335 } 336 337 /** The selected resources. */ 338 protected List<CmsResource> m_currentResources = new ArrayList<CmsResource>(); 339 340 /** The default action column property. */ 341 CmsResourceTableProperty m_actionColumnProperty; 342 343 /** The additional cell style generators. */ 344 List<Table.CellStyleGenerator> m_additionalStyleGenerators; 345 346 /** The current file property edit handler. */ 347 I_CmsFilePropertyEditHandler m_editHandler; 348 349 /** File edit event handler. */ 350 FileEditHandler m_fileEditHandler = new FileEditHandler(); 351 352 /** The context menu. */ 353 CmsContextMenu m_menu; 354 355 /** The context menu builder. */ 356 I_CmsContextMenuBuilder m_menuBuilder; 357 358 /** The table drag mode, stored during item editing. */ 359 private TableDragMode m_beforEditDragMode; 360 361 /** The dialog context provider. */ 362 private I_CmsContextProvider m_contextProvider; 363 364 /** The edited item id. */ 365 private CmsUUID m_editItemId; 366 367 /** The edited property id. */ 368 private CmsResourceTableProperty m_editProperty; 369 370 /** Saved container filters. */ 371 private Collection<Filter> m_filters = Collections.emptyList(); 372 373 /** The folder select handler. */ 374 private I_FolderSelectHandler m_folderSelectHandler; 375 376 /** The original edit value. */ 377 private String m_originalEditValue; 378 379 /** 380 * Default constructor.<p> 381 * 382 * @param contextProvider the dialog context provider 383 */ 384 public CmsFileTable(I_CmsContextProvider contextProvider) { 385 386 this(contextProvider, DEFAULT_TABLE_PROPERTIES); 387 } 388 389 /** 390 * Default constructor.<p> 391 * 392 * @param contextProvider the dialog context provider 393 * @param tableColumns the table columns to show 394 */ 395 public CmsFileTable(I_CmsContextProvider contextProvider, Map<CmsResourceTableProperty, Integer> tableColumns) { 396 397 super(); 398 m_additionalStyleGenerators = new ArrayList<Table.CellStyleGenerator>(); 399 m_actionColumnProperty = PROPERTY_RESOURCE_NAME; 400 m_contextProvider = contextProvider; 401 m_container.setItemSorter(new FileSorter()); 402 m_fileTable.addStyleName(ValoTheme.TABLE_BORDERLESS); 403 m_fileTable.addStyleName(OpenCmsTheme.SIMPLE_DRAG); 404 m_fileTable.setSizeFull(); 405 m_fileTable.setColumnCollapsingAllowed(true); 406 m_fileTable.setSelectable(true); 407 m_fileTable.setMultiSelect(true); 408 409 m_fileTable.setTableFieldFactory(new FileFieldFactory()); 410 ColumnBuilder builder = new ColumnBuilder(); 411 for (Entry<CmsResourceTableProperty, Integer> entry : tableColumns.entrySet()) { 412 builder.column(entry.getKey(), entry.getValue().intValue()); 413 } 414 builder.buildColumns(); 415 416 m_fileTable.setSortContainerPropertyId(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME); 417 m_menu = new CmsContextMenu(); 418 m_fileTable.addValueChangeListener(new ValueChangeListener() { 419 420 private static final long serialVersionUID = 1L; 421 422 public void valueChange(ValueChangeEvent event) { 423 424 @SuppressWarnings("unchecked") 425 Set<String> selectedIds = (Set<String>)event.getProperty().getValue(); 426 List<CmsResource> selectedResources = new ArrayList<CmsResource>(); 427 for (String id : selectedIds) { 428 try { 429 CmsResource resource = A_CmsUI.getCmsObject().readResource( 430 getUUIDFromItemID(id), 431 CmsResourceFilter.ALL); 432 selectedResources.add(resource); 433 } catch (CmsException e) { 434 LOG.error(e.getLocalizedMessage(), e); 435 } 436 437 } 438 m_currentResources = selectedResources; 439 440 rebuildMenu(); 441 } 442 }); 443 444 m_fileTable.addItemClickListener(new ItemClickListener() { 445 446 private static final long serialVersionUID = 1L; 447 448 public void itemClick(ItemClickEvent event) { 449 450 handleFileItemClick(event); 451 } 452 }); 453 454 m_fileTable.setCellStyleGenerator(new Table.CellStyleGenerator() { 455 456 private static final long serialVersionUID = 1L; 457 458 public String getStyle(Table source, Object itemId, Object propertyId) { 459 460 Item item = m_container.getItem(itemId); 461 String style = getStateStyle(item); 462 if (m_actionColumnProperty == propertyId) { 463 style += " " + OpenCmsTheme.HOVER_COLUMN; 464 } else if ((CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT == propertyId) 465 || (CmsResourceTableProperty.PROPERTY_TITLE == propertyId)) { 466 if ((item.getItemProperty(CmsResourceTableProperty.PROPERTY_IN_NAVIGATION) != null) 467 && ((Boolean)item.getItemProperty( 468 CmsResourceTableProperty.PROPERTY_IN_NAVIGATION).getValue()).booleanValue()) { 469 style += " " + OpenCmsTheme.IN_NAVIGATION; 470 } 471 } 472 for (Table.CellStyleGenerator generator : m_additionalStyleGenerators) { 473 String additional = generator.getStyle(source, itemId, propertyId); 474 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(additional)) { 475 style += " " + additional; 476 } 477 } 478 return style; 479 } 480 }); 481 482 m_menu.setAsTableContextMenu(m_fileTable); 483 } 484 485 /** 486 * Returns the resource state specific style name.<p> 487 * 488 * @param resourceItem the resource item 489 * 490 * @return the style name 491 */ 492 public static String getStateStyle(Item resourceItem) { 493 494 String result = ""; 495 if (resourceItem != null) { 496 if ((resourceItem.getItemProperty(PROPERTY_INSIDE_PROJECT) == null) 497 || ((Boolean)resourceItem.getItemProperty(PROPERTY_INSIDE_PROJECT).getValue()).booleanValue()) { 498 499 CmsResourceState state = (CmsResourceState)resourceItem.getItemProperty( 500 CmsResourceTableProperty.PROPERTY_STATE).getValue(); 501 result = getStateStyle(state); 502 } else { 503 result = OpenCmsTheme.PROJECT_OTHER; 504 } 505 if ((resourceItem.getItemProperty(PROPERTY_RELEASED_NOT_EXPIRED) != null) 506 && !((Boolean)resourceItem.getItemProperty(PROPERTY_RELEASED_NOT_EXPIRED).getValue()).booleanValue()) { 507 result += " " + OpenCmsTheme.EXPIRED; 508 } 509 if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED) != null) 510 && ((Boolean)resourceItem.getItemProperty( 511 CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) { 512 result += " " + OpenCmsTheme.DISABLED; 513 } 514 } 515 return result; 516 } 517 518 /** 519 * Adds an additional cell style generator.<p> 520 * 521 * @param styleGenerator the cell style generator 522 */ 523 public void addAdditionalStyleGenerator(Table.CellStyleGenerator styleGenerator) { 524 525 m_additionalStyleGenerators.add(styleGenerator); 526 } 527 528 /** 529 * Applies settings generally used within workplace app file lists.<p> 530 */ 531 public void applyWorkplaceAppSettings() { 532 533 // add site path property to container 534 m_container.addContainerProperty( 535 CmsResourceTableProperty.PROPERTY_SITE_PATH, 536 CmsResourceTableProperty.PROPERTY_SITE_PATH.getColumnType(), 537 CmsResourceTableProperty.PROPERTY_SITE_PATH.getDefaultValue()); 538 539 // replace the resource name column with the path column 540 Object[] visibleCols = m_fileTable.getVisibleColumns(); 541 for (int i = 0; i < visibleCols.length; i++) { 542 if (CmsResourceTableProperty.PROPERTY_RESOURCE_NAME.equals(visibleCols[i])) { 543 visibleCols[i] = CmsResourceTableProperty.PROPERTY_SITE_PATH; 544 } 545 } 546 m_fileTable.setVisibleColumns(visibleCols); 547 m_fileTable.setColumnCollapsible(CmsResourceTableProperty.PROPERTY_SITE_PATH, false); 548 m_fileTable.setColumnHeader( 549 CmsResourceTableProperty.PROPERTY_SITE_PATH, 550 CmsVaadinUtils.getMessageText(CmsResourceTableProperty.PROPERTY_SITE_PATH.getHeaderKey())); 551 552 // update column visibility according to the latest file explorer settings 553 CmsFileExplorerSettings settings; 554 try { 555 settings = OpenCms.getWorkplaceAppManager().getAppSettings( 556 A_CmsUI.getCmsObject(), 557 CmsFileExplorerSettings.class); 558 559 setTableState(settings); 560 } catch (Exception e) { 561 LOG.error("Error while reading file explorer settings from user.", e); 562 } 563 m_fileTable.setSortContainerPropertyId(CmsResourceTableProperty.PROPERTY_SITE_PATH); 564 setActionColumnProperty(CmsResourceTableProperty.PROPERTY_SITE_PATH); 565 setMenuBuilder(new CmsResourceContextMenuBuilder()); 566 } 567 568 /** 569 * Clears all container filters. 570 */ 571 public void clearFilters() { 572 573 IndexedContainer container = (IndexedContainer)m_fileTable.getContainerDataSource(); 574 container.removeAllContainerFilters(); 575 } 576 577 /** 578 * Checks if the file table has a row for the resource with the given structure id. 579 * 580 * @param structureId a structure id 581 * @return true if the file table has a row for the resource with the given id 582 */ 583 public boolean containsId(CmsUUID structureId) { 584 585 return m_fileTable.getContainerDataSource().getItem("" + structureId) != null; 586 } 587 588 /** 589 * Filters the displayed resources.<p> 590 * Only resources where either the resource name, the title or the nav-text contains the given substring are shown.<p> 591 * 592 * @param search the search term 593 */ 594 public void filterTable(String search) { 595 596 m_container.removeAllContainerFilters(); 597 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(search)) { 598 m_container.addContainerFilter( 599 new Or( 600 new SimpleStringFilter(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, search, true, false), 601 new SimpleStringFilter(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT, search, true, false), 602 new SimpleStringFilter(CmsResourceTableProperty.PROPERTY_TITLE, search, true, false))); 603 } 604 if ((m_fileTable.getValue() != null) & !((Set<?>)m_fileTable.getValue()).isEmpty()) { 605 m_fileTable.setCurrentPageFirstItemId(((Set<?>)m_fileTable.getValue()).iterator().next()); 606 } 607 } 608 609 /** 610 * Generates UTF-8 encoded CSV for currently active table columns (standard columns only). 611 * 612 * <p>Note: the generated CSV takes the active filters into account. 613 * 614 * @return the generated CSV data 615 */ 616 public byte[] generateCsv() { 617 618 try { 619 Container container = m_fileTable.getContainerDataSource(); 620 Object[] columnArray = m_fileTable.getVisibleColumns(); 621 Set<Object> columns = new HashSet<>(Arrays.asList(columnArray)); 622 LinkedHashMap<Object, Function<Object, String>> columnFormatters = new LinkedHashMap<>(); 623 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 624 CSVWriter writer = new CSVWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8)); 625 List<String> csvHeaders = new ArrayList<>(); 626 List<CmsResourceTableProperty> csvColumns = new ArrayList<>(); 627 628 for (Object propId : m_fileTable.getVisibleColumns()) { 629 if (propId instanceof CmsResourceTableProperty) { 630 CmsResourceTableProperty tableProp = (CmsResourceTableProperty)propId; 631 if (!m_fileTable.isColumnCollapsed(propId)) { 632 Class<?> colType = tableProp.getColumnType(); 633 // skip "widget"-valued columns - currently this is just the project flag 634 if (!colType.getName().contains("vaadin")) { 635 // always use English column headers, as external tools using the CSV may use the column labels as IDs 636 String colHeader = OpenCms.getWorkplaceManager().getMessages(Locale.ENGLISH).key( 637 tableProp.getHeaderKey()); 638 csvHeaders.add(colHeader); 639 csvColumns.add(tableProp); 640 } 641 } 642 } 643 } 644 645 final String[] emptyArray = {}; 646 writer.writeNext(csvHeaders.toArray(emptyArray)); 647 final DateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 648 iso8601.setTimeZone(TimeZone.getTimeZone("UTC")); 649 650 Set<CmsResourceTableProperty> dateCols = new HashSet<>( 651 Arrays.asList( 652 CmsResourceTableProperty.PROPERTY_DATE_CREATED, 653 CmsResourceTableProperty.PROPERTY_DATE_MODIFIED, 654 CmsResourceTableProperty.PROPERTY_DATE_RELEASED, 655 CmsResourceTableProperty.PROPERTY_DATE_EXPIRED)); 656 for (Object itemId : m_fileTable.getContainerDataSource().getItemIds()) { 657 Item item = m_fileTable.getContainerDataSource().getItem(itemId); 658 List<String> row = new ArrayList<>(); 659 for (CmsResourceTableProperty col : csvColumns) { 660 Object value = item.getItemProperty(col).getValue(); 661 // render nulls as empty strings, some special "Long"-valued date columns as ISO8601 time stamps, and just use toString() for everything else 662 String csvValue = ""; 663 if (value != null) { 664 if (dateCols.contains(col) && (value instanceof Long)) { 665 csvValue = iso8601.format(new Date((Long)value)); 666 } else { 667 csvValue = value.toString(); 668 } 669 } 670 row.add(csvValue); 671 } 672 writer.writeNext(row.toArray(emptyArray)); 673 } 674 writer.flush(); 675 byte[] result = baos.toByteArray(); 676 return result; 677 } catch (Exception e) { 678 LOG.error(e.getLocalizedMessage(), e); 679 return null; 680 } 681 } 682 683 /** 684 * Returns the dialog context provider.<p> 685 * 686 * @return the dialog context provider 687 */ 688 public I_CmsContextProvider getContextProvider() { 689 690 return m_contextProvider; 691 } 692 693 /** 694 * Returns the index of the first visible item.<p> 695 * 696 * @return the first visible item 697 */ 698 public int getFirstVisibleItemIndex() { 699 700 return m_fileTable.getCurrentPageFirstItemIndex(); 701 } 702 703 /** 704 * Gets the selected structure ids.<p> 705 * 706 * @return the set of selected structure ids 707 */ 708 @SuppressWarnings("unchecked") 709 public Collection<CmsUUID> getSelectedIds() { 710 711 return itemIdsToUUIDs((Collection<String>)m_fileTable.getValue()); 712 } 713 714 /** 715 * Gets the list of selected resources.<p> 716 * 717 * @return the list of selected resources 718 */ 719 public List<CmsResource> getSelectedResources() { 720 721 return m_currentResources; 722 } 723 724 /** 725 * Returns the current table state.<p> 726 * 727 * @return the table state 728 */ 729 public CmsFileExplorerSettings getTableSettings() { 730 731 CmsFileExplorerSettings fileTableState = new CmsFileExplorerSettings(); 732 733 fileTableState.setSortAscending(m_fileTable.isSortAscending()); 734 fileTableState.setSortColumnId((CmsResourceTableProperty)m_fileTable.getSortContainerPropertyId()); 735 List<CmsResourceTableProperty> collapsedCollumns = new ArrayList<CmsResourceTableProperty>(); 736 Object[] visibleCols = m_fileTable.getVisibleColumns(); 737 for (int i = 0; i < visibleCols.length; i++) { 738 if (m_fileTable.isColumnCollapsed(visibleCols[i])) { 739 collapsedCollumns.add((CmsResourceTableProperty)visibleCols[i]); 740 } 741 } 742 fileTableState.setCollapsedColumns(collapsedCollumns); 743 return fileTableState; 744 } 745 746 /** 747 * Handles the item selection.<p> 748 * 749 * @param itemId the selected item id 750 */ 751 public void handleSelection(String itemId) { 752 753 Collection<?> selection = (Collection<?>)m_fileTable.getValue(); 754 if (selection == null) { 755 m_fileTable.select(itemId); 756 } else if (!selection.contains(itemId)) { 757 m_fileTable.setValue(null); 758 m_fileTable.select(itemId); 759 } 760 } 761 762 /** 763 * Returns if a file property is being edited.<p> 764 * @return <code>true</code> if a file property is being edited 765 */ 766 public boolean isEditing() { 767 768 return m_editItemId != null; 769 } 770 771 /** 772 * Returns if the given property is being edited.<p> 773 * 774 * @param propertyId the property id 775 * 776 * @return <code>true</code> if the given property is being edited 777 */ 778 public boolean isEditProperty(CmsResourceTableProperty propertyId) { 779 780 return (m_editProperty != null) && m_editProperty.equals(propertyId); 781 } 782 783 /** 784 * Opens the context menu.<p> 785 * 786 * @param event the click event 787 */ 788 public void openContextMenu(ItemClickEvent event) { 789 790 m_menu.openForTable(event, m_fileTable); 791 } 792 793 /** 794 * Removes the given cell style generator.<p> 795 * 796 * @param styleGenerator the cell style generator to remove 797 */ 798 public void removeAdditionalStyleGenerator(Table.CellStyleGenerator styleGenerator) { 799 800 m_additionalStyleGenerators.remove(styleGenerator); 801 } 802 803 /** 804 * Restores container filters to the ones previously saved via saveFilters(). 805 */ 806 public void restoreFilters() { 807 808 IndexedContainer container = (IndexedContainer)m_fileTable.getContainerDataSource(); 809 container.removeAllContainerFilters(); 810 for (Filter filter : m_filters) { 811 container.addContainerFilter(filter); 812 } 813 } 814 815 /** 816 * Saves currently active filters.<p> 817 */ 818 public void saveFilters() { 819 820 IndexedContainer container = (IndexedContainer)m_fileTable.getContainerDataSource(); 821 m_filters = container.getContainerFilters(); 822 } 823 824 /** 825 * Sets the default action column property.<p> 826 * 827 * @param actionColumnProperty the default action column property 828 */ 829 public void setActionColumnProperty(CmsResourceTableProperty actionColumnProperty) { 830 831 m_actionColumnProperty = actionColumnProperty; 832 } 833 834 /** 835 * Sets the dialog context provider.<p> 836 * 837 * @param provider the dialog context provider 838 */ 839 public void setContextProvider(I_CmsContextProvider provider) { 840 841 m_contextProvider = provider; 842 } 843 844 /** 845 * Sets the first visible item index.<p> 846 * 847 * @param i the item index 848 */ 849 public void setFirstVisibleItemIndex(int i) { 850 851 m_fileTable.setCurrentPageFirstItemIndex(i); 852 } 853 854 /** 855 * Sets the folder select handler.<p> 856 * 857 * @param folderSelectHandler the folder select handler 858 */ 859 public void setFolderSelectHandler(I_FolderSelectHandler folderSelectHandler) { 860 861 m_folderSelectHandler = folderSelectHandler; 862 } 863 864 /** 865 * Sets the menu builder.<p> 866 * 867 * @param builder the menu builder 868 */ 869 public void setMenuBuilder(I_CmsContextMenuBuilder builder) { 870 871 m_menuBuilder = builder; 872 } 873 874 /** 875 * Sets the table state.<p> 876 * 877 * @param state the table state 878 */ 879 public void setTableState(CmsFileExplorerSettings state) { 880 881 if (state != null) { 882 m_fileTable.setSortContainerPropertyId(state.getSortColumnId()); 883 m_fileTable.setSortAscending(state.isSortAscending()); 884 Object[] visibleCols = m_fileTable.getVisibleColumns(); 885 for (int i = 0; i < visibleCols.length; i++) { 886 m_fileTable.setColumnCollapsed(visibleCols[i], state.getCollapsedColumns().contains(visibleCols[i])); 887 } 888 } 889 } 890 891 /** 892 * Starts inline editing of the given file property.<p> 893 * 894 * @param itemId the item resource structure id 895 * @param propertyId the property to edit 896 * @param editHandler the edit handler 897 */ 898 public void startEdit( 899 CmsUUID itemId, 900 CmsResourceTableProperty propertyId, 901 I_CmsFilePropertyEditHandler editHandler) { 902 903 m_editItemId = itemId; 904 m_editProperty = propertyId; 905 m_originalEditValue = (String)m_container.getItem(m_editItemId.toString()).getItemProperty( 906 m_editProperty).getValue(); 907 m_editHandler = editHandler; 908 909 // storing current drag mode and setting it to none to avoid text selection issues in IE11 910 m_beforEditDragMode = m_fileTable.getDragMode(); 911 m_fileTable.setDragMode(TableDragMode.NONE); 912 913 m_fileTable.setEditable(true); 914 } 915 916 /** 917 * Stops the current edit process to save the changed property value.<p> 918 */ 919 public void stopEdit() { 920 921 if (m_editHandler != null) { 922 String value = (String)m_container.getItem(m_editItemId.toString()).getItemProperty( 923 m_editProperty).getValue(); 924 if (!value.equals(m_originalEditValue)) { 925 m_editHandler.validate(value); 926 m_editHandler.save(value); 927 } else { 928 // call cancel to ensure unlock 929 m_editHandler.cancel(); 930 } 931 } 932 clearEdit(); 933 934 // restoring drag mode 935 m_fileTable.setDragMode(m_beforEditDragMode); 936 937 m_beforEditDragMode = null; 938 } 939 940 /** 941 * Updates all items with ids from the given list.<p> 942 * 943 * @param ids the resource structure ids to update 944 * @param remove true if the item should be removed only 945 */ 946 public void update(Collection<CmsUUID> ids, boolean remove) { 947 948 for (CmsUUID id : ids) { 949 updateItem(id, remove); 950 } 951 rebuildMenu(); 952 } 953 954 /** 955 * Updates the column widths.<p> 956 * 957 * The reason this is needed is that the Vaadin table does not support minimum widths for columns, 958 * so expanding columns get squished when most of the horizontal space is used by other columns. 959 * So we try to determine whether the expanded columns would have enough space, and if not, give them a 960 * fixed width. 961 * 962 * @param estimatedSpace the estimated horizontal space available for the table. 963 */ 964 public void updateColumnWidths(int estimatedSpace) { 965 966 Object[] cols = m_fileTable.getVisibleColumns(); 967 List<CmsResourceTableProperty> expandCols = Lists.newArrayList(); 968 int nonExpandWidth = 0; 969 int totalExpandMinWidth = 0; 970 for (Object colObj : cols) { 971 if (m_fileTable.isColumnCollapsed(colObj)) { 972 continue; 973 } 974 CmsResourceTableProperty prop = (CmsResourceTableProperty)colObj; 975 if (0 < m_fileTable.getColumnExpandRatio(prop)) { 976 expandCols.add(prop); 977 totalExpandMinWidth += getAlternativeWidthForExpandingColumns(prop); 978 } else { 979 nonExpandWidth += prop.getColumnWidth(); 980 } 981 } 982 if (estimatedSpace < (totalExpandMinWidth + nonExpandWidth)) { 983 for (CmsResourceTableProperty expandCol : expandCols) { 984 m_fileTable.setColumnWidth(expandCol, getAlternativeWidthForExpandingColumns(expandCol)); 985 } 986 } 987 } 988 989 /** 990 * Updates the file table sorting.<p> 991 */ 992 public void updateSorting() { 993 994 m_fileTable.sort(); 995 } 996 997 /** 998 * Cancels the current edit process.<p> 999 */ 1000 void cancelEdit() { 1001 1002 if (m_editHandler != null) { 1003 m_editHandler.cancel(); 1004 } 1005 clearEdit(); 1006 } 1007 1008 /** 1009 * Returns the edit item id.<p> 1010 * 1011 * @return the edit item id 1012 */ 1013 CmsUUID getEditItemId() { 1014 1015 return m_editItemId; 1016 } 1017 1018 /** 1019 * Returns the edit property id.<p> 1020 * 1021 * @return the edit property id 1022 */ 1023 CmsResourceTableProperty getEditProperty() { 1024 1025 return m_editProperty; 1026 } 1027 1028 /** 1029 * Handles the file table item click.<p> 1030 * 1031 * @param event the click event 1032 */ 1033 void handleFileItemClick(ItemClickEvent event) { 1034 1035 if (isEditing()) { 1036 stopEdit(); 1037 1038 } else if (!event.isCtrlKey() && !event.isShiftKey()) { 1039 // don't interfere with multi-selection using control key 1040 String itemId = (String)event.getItemId(); 1041 CmsUUID structureId = getUUIDFromItemID(itemId); 1042 boolean openedFolder = false; 1043 if (event.getButton().equals(MouseButton.RIGHT)) { 1044 handleSelection(itemId); 1045 openContextMenu(event); 1046 } else { 1047 if ((event.getPropertyId() == null) 1048 || CmsResourceTableProperty.PROPERTY_TYPE_ICON.equals(event.getPropertyId())) { 1049 handleSelection(itemId); 1050 openContextMenu(event); 1051 } else { 1052 if (m_actionColumnProperty.equals(event.getPropertyId())) { 1053 Boolean isFolder = (Boolean)event.getItem().getItemProperty( 1054 CmsResourceTableProperty.PROPERTY_IS_FOLDER).getValue(); 1055 if ((isFolder != null) && isFolder.booleanValue()) { 1056 if (m_folderSelectHandler != null) { 1057 m_folderSelectHandler.onFolderSelect(structureId); 1058 } 1059 openedFolder = true; 1060 } else { 1061 try { 1062 CmsObject cms = A_CmsUI.getCmsObject(); 1063 CmsResource res = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 1064 m_currentResources = Collections.singletonList(res); 1065 I_CmsDialogContext context = m_contextProvider.getDialogContext(); 1066 I_CmsDefaultAction action = OpenCms.getWorkplaceAppManager().getDefaultAction( 1067 context, 1068 m_menuBuilder); 1069 if (action != null) { 1070 action.executeAction(context); 1071 return; 1072 } 1073 } catch (CmsVfsResourceNotFoundException e) { 1074 LOG.info(e.getLocalizedMessage(), e); 1075 } catch (CmsException e) { 1076 LOG.error(e.getLocalizedMessage(), e); 1077 } 1078 } 1079 } else { 1080 I_CmsDialogContext context = m_contextProvider.getDialogContext(); 1081 if ((m_currentResources.size() == 1) 1082 && m_currentResources.get(0).getStructureId().equals(structureId) 1083 && (context instanceof I_CmsEditPropertyContext) 1084 && ((I_CmsEditPropertyContext)context).isPropertyEditable(event.getPropertyId())) { 1085 1086 ((I_CmsEditPropertyContext)context).editProperty(event.getPropertyId()); 1087 } 1088 } 1089 } 1090 } 1091 // update the item on click to show any available changes 1092 if (!openedFolder) { 1093 update(Collections.singletonList(structureId), false); 1094 } 1095 } 1096 } 1097 1098 /** 1099 * Rebuilds the context menu.<p> 1100 */ 1101 void rebuildMenu() { 1102 1103 if (!getSelectedIds().isEmpty() && (m_menuBuilder != null)) { 1104 m_menu.removeAllItems(); 1105 m_menuBuilder.buildContextMenu(getContextProvider().getDialogContext(), m_menu); 1106 } 1107 } 1108 1109 /** 1110 * Clears the current edit process.<p> 1111 */ 1112 private void clearEdit() { 1113 1114 m_fileTable.setEditable(false); 1115 if (m_editItemId != null) { 1116 updateItem(m_editItemId, false); 1117 } 1118 m_editItemId = null; 1119 m_editProperty = null; 1120 m_editHandler = null; 1121 updateSorting(); 1122 } 1123 1124 /** 1125 * Gets alternative width for expanding table columns which is used when there is not enough space for 1126 * all visible columns.<p> 1127 * 1128 * @param prop the table property 1129 * @return the alternative column width 1130 */ 1131 private int getAlternativeWidthForExpandingColumns(CmsResourceTableProperty prop) { 1132 1133 if (prop.getId().equals(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME.getId())) { 1134 return 200; 1135 } 1136 if (prop.getId().equals(CmsResourceTableProperty.PROPERTY_TITLE.getId())) { 1137 return 300; 1138 } 1139 if (prop.getId().equals(CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT.getId())) { 1140 return 200; 1141 } 1142 return 200; 1143 } 1144 1145 /** 1146 * Updates the given item in the file table.<p> 1147 * 1148 * @param itemId the item id 1149 * @param remove true if the item should be removed only 1150 */ 1151 private void updateItem(CmsUUID itemId, boolean remove) { 1152 1153 if (remove) { 1154 String idStr = itemId != null ? itemId.toString() : null; 1155 m_container.removeItem(idStr); 1156 return; 1157 } 1158 1159 CmsObject cms = A_CmsUI.getCmsObject(); 1160 try { 1161 CmsResource resource = cms.readResource(itemId, CmsResourceFilter.ALL); 1162 fillItem(cms, resource, OpenCms.getWorkplaceManager().getWorkplaceLocale(cms)); 1163 1164 } catch (CmsVfsResourceNotFoundException e) { 1165 if (null != itemId) { 1166 m_container.removeItem(itemId.toString()); 1167 } 1168 LOG.debug("Failed to update file table item, removing it from view.", e); 1169 } catch (CmsException e) { 1170 LOG.error(e.getLocalizedMessage(), e); 1171 } 1172 } 1173 1174}