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.dialogs.availability; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsProperty; 032import org.opencms.file.CmsPropertyDefinition; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResourceFilter; 035import org.opencms.file.types.CmsResourceTypeXmlContent; 036import org.opencms.file.types.I_CmsResourceType; 037import org.opencms.gwt.shared.CmsPrincipalBean; 038import org.opencms.loader.CmsLoaderException; 039import org.opencms.lock.CmsLockActionRecord; 040import org.opencms.lock.CmsLockActionRecord.LockChange; 041import org.opencms.lock.CmsLockException; 042import org.opencms.lock.CmsLockUtil; 043import org.opencms.main.CmsException; 044import org.opencms.main.CmsLog; 045import org.opencms.main.OpenCms; 046import org.opencms.security.CmsAccessControlEntry; 047import org.opencms.security.I_CmsPrincipal; 048import org.opencms.ui.A_CmsUI; 049import org.opencms.ui.CmsVaadinUtils; 050import org.opencms.ui.I_CmsDialogContext; 051import org.opencms.ui.components.CmsBasicDialog; 052import org.opencms.ui.components.CmsDateField; 053import org.opencms.ui.components.CmsOkCancelActionHandler; 054import org.opencms.ui.components.CmsResourceInfo; 055import org.opencms.util.CmsStringUtil; 056import org.opencms.util.CmsUUID; 057import org.opencms.workplace.CmsWorkplace; 058import org.opencms.workplace.CmsWorkplaceSettings; 059 060import java.util.ArrayList; 061import java.util.Collections; 062import java.util.Date; 063import java.util.HashMap; 064import java.util.Iterator; 065import java.util.List; 066import java.util.Map; 067 068import org.apache.commons.logging.Log; 069 070import com.vaadin.ui.Button; 071import com.vaadin.ui.Button.ClickEvent; 072import com.vaadin.ui.Button.ClickListener; 073import com.vaadin.ui.Panel; 074import com.vaadin.v7.data.Property.ValueChangeEvent; 075import com.vaadin.v7.data.Property.ValueChangeListener; 076import com.vaadin.v7.data.Validator; 077import com.vaadin.v7.ui.CheckBox; 078import com.vaadin.v7.ui.TextField; 079import com.vaadin.v7.ui.VerticalLayout; 080 081/** 082 * Availability dialog.<p> 083 */ 084public class CmsAvailabilityDialog extends CmsBasicDialog { 085 086 /** Preference key for the option to automatically include children. */ 087 public static final String PREF_AVAILABILITY_INCLUDE_CHILDREN_DEFAULT = "availabilityIncludeChildrenDefault"; 088 089 /** Logger for this class. */ 090 private static final Log LOG = CmsLog.getLog(CmsAvailabilityDialog.class); 091 092 /** Serial version id. */ 093 private static final long serialVersionUID = 1L; 094 095 /** Availability info. */ 096 private CmsAvailabilityInfoBean m_availabilityInfo; 097 098 /** The cancel button. */ 099 private Button m_cancelButton; 100 101 /** The dialog context. */ 102 private I_CmsDialogContext m_dialogContext; 103 104 /** Date field. */ 105 private CmsDateField m_expiredField; 106 107 /** Initial value for 'notification enabled. */ 108 private Boolean m_initialNotificationEnabled = Boolean.FALSE; 109 110 /** Initial value for notification interval. */ 111 private String m_initialNotificationInterval = ""; 112 113 /** 'Modify siblings' check box. */ 114 private CheckBox m_modifySiblingsField; 115 116 /** 'enable notification' check box. */ 117 private CheckBox m_notificationEnabledField; 118 119 /** Field for the notification interval. */ 120 private TextField m_notificationIntervalField; 121 122 /** Panel for notifications. */ 123 private Panel m_notificationPanel; 124 125 /** OK button. */ 126 private Button m_okButton; 127 128 /** Date field. */ 129 private CmsDateField m_releasedField; 130 131 /** Option to reset the expiration. */ 132 private CheckBox m_resetExpired; 133 134 /** Option to reset the relase date. */ 135 private CheckBox m_resetReleased; 136 137 /** Container for responsibles widgets. */ 138 private VerticalLayout m_responsiblesContainer; 139 140 /** Panel for 'Responsibles'. */ 141 private Panel m_responsiblesPanel; 142 143 /** Option to enable subresource modification. */ 144 private CheckBox m_subresourceModificationField; 145 146 /** 147 * Creates a new instance.<p> 148 * 149 * @param dialogContext the dialog context 150 */ 151 public CmsAvailabilityDialog(I_CmsDialogContext dialogContext) { 152 153 super(); 154 m_dialogContext = dialogContext; 155 CmsObject cms = dialogContext.getCms(); 156 CmsVaadinUtils.readAndLocalizeDesign( 157 this, 158 OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale()), 159 null); 160 List<CmsResource> resources = dialogContext.getResources(); 161 m_notificationIntervalField.addValidator(new Validator() { 162 163 /** Serial version id. */ 164 private static final long serialVersionUID = 1L; 165 166 public void validate(Object value) throws InvalidValueException { 167 168 String strValue = ((String)value).trim(); 169 if (!strValue.matches("[0-9]*")) { 170 throw new InvalidValueException( 171 CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_VALIDATOR_EMPTY_OR_NUMBER_0)); 172 173 } 174 175 } 176 }); 177 178 boolean hasSiblings = false; 179 for (CmsResource resource : m_dialogContext.getResources()) { 180 hasSiblings |= resource.getSiblingCount() > 1; 181 if (hasSiblings) { 182 break; 183 } 184 } 185 m_modifySiblingsField.setVisible(hasSiblings); 186 187 if (resources.size() == 1) { 188 CmsResource onlyResource = resources.get(0); 189 if (onlyResource.getDateReleased() != CmsResource.DATE_RELEASED_DEFAULT) { 190 m_releasedField.setDate(new Date(onlyResource.getDateReleased())); 191 } 192 if (onlyResource.getDateExpired() != CmsResource.DATE_EXPIRED_DEFAULT) { 193 m_expiredField.setDate(new Date(onlyResource.getDateExpired())); 194 } 195 initNotification(); 196 Map<CmsPrincipalBean, String> responsibles = m_availabilityInfo.getResponsibles(); 197 if (!responsibles.isEmpty()) { 198 m_responsiblesPanel.setVisible(true); 199 m_notificationPanel.setVisible(true); 200 for (Map.Entry<CmsPrincipalBean, String> entry : responsibles.entrySet()) { 201 CmsPrincipalBean principal = entry.getKey(); 202 String icon = principal.isGroup() 203 ? CmsWorkplace.getResourceUri("buttons/group.png") 204 : CmsWorkplace.getResourceUri("buttons/user.png"); 205 String subtitle = ""; 206 if (entry.getValue() != null) { 207 subtitle = cms.getRequestContext().removeSiteRoot(entry.getValue()); 208 } 209 CmsResourceInfo infoWidget = new CmsResourceInfo(entry.getKey().getName(), subtitle, icon); 210 m_responsiblesContainer.addComponent(infoWidget); 211 212 } 213 } 214 } else { 215 boolean showNotification = false; 216 resourceLoop: for (CmsResource resource : resources) { 217 try { 218 List<CmsAccessControlEntry> aces = cms.getAccessControlEntries(cms.getSitePath(resource)); 219 for (CmsAccessControlEntry ace : aces) { 220 if (ace.isResponsible()) { 221 showNotification = true; 222 break resourceLoop; 223 } 224 } 225 } catch (CmsException e) { 226 LOG.error(e.getLocalizedMessage(), e); 227 } 228 } 229 m_notificationPanel.setVisible(showNotification); 230 } 231 m_notificationEnabledField.setValue(m_initialNotificationEnabled); 232 m_notificationIntervalField.setValue(m_initialNotificationInterval); 233 boolean hasFolders = false; 234 for (CmsResource resource : resources) { 235 if (resource.isFolder()) { 236 hasFolders = true; 237 } 238 } 239 240 CmsWorkplaceSettings wpSettings = A_CmsUI.get().getWorkplaceSettings(); 241 boolean includeChildren = Boolean.parseBoolean( 242 wpSettings.getUserSettings().getAdditionalPreference(PREF_AVAILABILITY_INCLUDE_CHILDREN_DEFAULT, true)); 243 m_subresourceModificationField.setVisible(hasFolders); 244 m_subresourceModificationField.setValue(Boolean.valueOf(includeChildren)); 245 246 initResetCheckbox(m_resetReleased, m_releasedField); 247 initResetCheckbox(m_resetExpired, m_expiredField); 248 m_okButton.addClickListener(new ClickListener() { 249 250 private static final long serialVersionUID = 1L; 251 252 public void buttonClick(ClickEvent event) { 253 254 submit(); 255 } 256 257 }); 258 259 m_cancelButton.addClickListener(new ClickListener() { 260 261 private static final long serialVersionUID = 1L; 262 263 public void buttonClick(ClickEvent event) { 264 265 cancel(); 266 } 267 }); 268 displayResourceInfo(m_dialogContext.getResources()); 269 270 setActionHandler(new CmsOkCancelActionHandler() { 271 272 private static final long serialVersionUID = 1L; 273 274 @Override 275 protected void cancel() { 276 277 CmsAvailabilityDialog.this.cancel(); 278 } 279 280 @Override 281 protected void ok() { 282 283 submit(); 284 } 285 }); 286 } 287 288 /** 289 * Initializes the values for the notification widgets.<p> 290 */ 291 public void initNotification() { 292 293 if (m_dialogContext.getResources().size() == 1) { 294 CmsResource resource = m_dialogContext.getResources().get(0); 295 try { 296 m_availabilityInfo = getAvailabilityInfo(A_CmsUI.getCmsObject(), resource); 297 m_initialNotificationInterval = "" + m_availabilityInfo.getNotificationInterval(); 298 m_initialNotificationEnabled = Boolean.valueOf(m_availabilityInfo.isNotificationEnabled()); 299 } catch (CmsLoaderException e) { 300 LOG.error(e.getLocalizedMessage(), e); 301 } catch (CmsException e) { 302 LOG.error(e.getLocalizedMessage(), e); 303 } 304 305 } 306 } 307 308 /** 309 * Actually performs the availability change.<p> 310 * 311 * @return the ids of the changed resources 312 * 313 * @throws CmsException if something goes wrong 314 */ 315 protected List<CmsUUID> changeAvailability() throws CmsException { 316 317 Date released = m_releasedField.getDate(); 318 Date expired = m_expiredField.getDate(); 319 boolean resetReleased = m_resetReleased.getValue().booleanValue(); 320 boolean resetExpired = m_resetExpired.getValue().booleanValue(); 321 boolean modifySubresources = m_subresourceModificationField.getValue().booleanValue(); 322 List<CmsUUID> changedIds = new ArrayList<CmsUUID>(); 323 for (CmsResource resource : m_dialogContext.getResources()) { 324 changeAvailability(resource, released, resetReleased, expired, resetExpired, modifySubresources); 325 changedIds.add(resource.getStructureId()); 326 } 327 328 String notificationInterval = m_notificationIntervalField.getValue().trim(); 329 int notificationIntervalInt = 0; 330 try { 331 notificationIntervalInt = Integer.parseInt(notificationInterval); 332 } catch (NumberFormatException e) { 333 LOG.warn(e.getLocalizedMessage(), e); 334 } 335 Boolean notificationEnabled = m_notificationEnabledField.getValue(); 336 boolean notificationSettingsUnchanged = notificationInterval.equals(m_initialNotificationInterval) 337 && notificationEnabled.equals(m_initialNotificationEnabled); 338 339 CmsObject cms = A_CmsUI.getCmsObject(); 340 if (!notificationSettingsUnchanged) { 341 for (CmsResource resource : m_dialogContext.getResources()) { 342 performSingleResourceNotification( 343 A_CmsUI.getCmsObject(), 344 cms.getSitePath(resource), 345 notificationEnabled.booleanValue(), 346 notificationIntervalInt, 347 m_modifySiblingsField.getValue().booleanValue()); 348 } 349 350 } 351 return changedIds; 352 } 353 354 /** 355 * Cancels the dialog.<p> 356 */ 357 void cancel() { 358 359 m_dialogContext.finish(new ArrayList<CmsUUID>()); 360 } 361 362 /** 363 * Returns the availability info.<p> 364 * 365 * @param cms the cms context 366 * @param res the resource 367 * 368 * @return the info 369 * 370 * @throws CmsException if reading the info fails 371 */ 372 CmsAvailabilityInfoBean getAvailabilityInfo(CmsObject cms, CmsResource res) throws CmsException { 373 374 CmsAvailabilityInfoBean result = new CmsAvailabilityInfoBean(); 375 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(res.getTypeId()); 376 result.setResType(type.getTypeName()); 377 378 result.setDateReleased(res.getDateReleased()); 379 result.setDateExpired(res.getDateExpired()); 380 381 String notificationInterval = cms.readPropertyObject( 382 res, 383 CmsPropertyDefinition.PROPERTY_NOTIFICATION_INTERVAL, 384 false).getValue(); 385 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(notificationInterval)) { 386 result.setNotificationInterval(Integer.valueOf(notificationInterval).intValue()); 387 } 388 389 String notificationEnabled = cms.readPropertyObject( 390 res, 391 CmsPropertyDefinition.PROPERTY_ENABLE_NOTIFICATION, 392 false).getValue(); 393 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(notificationEnabled)) { 394 result.setNotificationEnabled(Boolean.valueOf(notificationEnabled).booleanValue()); 395 } 396 397 result.setHasSiblings(cms.readSiblings(res, CmsResourceFilter.ALL).size() > 1); 398 399 result.setResponsibles(getResponsibles(cms, res)); 400 401 return result; 402 } 403 404 /** 405 * Returns the responsibles.<p> 406 * 407 * @param cms the cms context 408 * @param res the resource 409 * 410 * @return the responsibles 411 */ 412 Map<CmsPrincipalBean, String> getResponsibles(CmsObject cms, CmsResource res) { 413 414 Map<CmsPrincipalBean, String> result = new HashMap<CmsPrincipalBean, String>(); 415 List<CmsResource> parentResources = new ArrayList<CmsResource>(); 416 // get all parent folders of the current file 417 try { 418 parentResources = cms.readPath(res, CmsResourceFilter.IGNORE_EXPIRATION); 419 } catch (CmsException e) { 420 LOG.error(e.getLocalizedMessage(), e); 421 } 422 423 for (CmsResource resource : parentResources) { 424 String storedSiteRoot = cms.getRequestContext().getSiteRoot(); 425 String sitePath = cms.getRequestContext().removeSiteRoot(resource.getRootPath()); 426 try { 427 cms.getRequestContext().setSiteRoot("/"); 428 List<CmsAccessControlEntry> entries = cms.getAccessControlEntries(resource.getRootPath(), false); 429 for (CmsAccessControlEntry ace : entries) { 430 if (ace.isResponsible()) { 431 I_CmsPrincipal principal = cms.lookupPrincipal(ace.getPrincipal()); 432 if (principal != null) { 433 CmsPrincipalBean prinBean = new CmsPrincipalBean( 434 principal.getName(), 435 principal.getDescription(), 436 principal.isGroup()); 437 if (!resource.getRootPath().equals(res.getRootPath())) { 438 if (resource.getRootPath().startsWith(storedSiteRoot)) { 439 result.put(prinBean, sitePath); 440 } else { 441 result.put(prinBean, resource.getRootPath()); 442 } 443 } else { 444 result.put(prinBean, null); 445 } 446 } 447 } 448 } 449 } catch (CmsException e) { 450 LOG.info( 451 "Problem with reading responsible users for " 452 + resource.getName() 453 + " : " 454 + e.getLocalizedMessage(), 455 e); 456 } finally { 457 cms.getRequestContext().setSiteRoot(storedSiteRoot); 458 } 459 } 460 return result; 461 } 462 463 /** 464 * Submits the dialog.<p> 465 */ 466 void submit() { 467 468 try { 469 m_dialogContext.finish(changeAvailability()); 470 } catch (Throwable t) { 471 m_dialogContext.error(t); 472 } 473 } 474 475 /** 476 * Changes availability.<p> 477 * 478 * @param resource the resource 479 * @param released release date 480 * @param resetReleased reset release date 481 * @param expired expiration date 482 * @param resetExpired reset expiration date 483 * @param modifySubresources modify children 484 * 485 * @throws CmsException if something goes wrong 486 */ 487 private void changeAvailability( 488 CmsResource resource, 489 Date released, 490 boolean resetReleased, 491 Date expired, 492 boolean resetExpired, 493 boolean modifySubresources) 494 throws CmsException { 495 496 CmsObject cms = m_dialogContext.getCms(); 497 CmsLockActionRecord lockActionRecord = CmsLockUtil.ensureLock(cms, resource); 498 try { 499 cms.getRequestContext().setAttribute( 500 CmsResourceTypeXmlContent.ATTR_REVERSE_AVAILABILITY_MAPPING, 501 Boolean.TRUE); 502 long newDateReleased; 503 if (resetReleased || (released != null)) { 504 newDateReleased = released != null ? released.getTime() : CmsResource.DATE_RELEASED_DEFAULT; 505 cms.setDateReleased(resource, newDateReleased, modifySubresources); 506 } 507 long newDateExpired; 508 if (resetExpired || (expired != null)) { 509 newDateExpired = expired != null ? expired.getTime() : CmsResource.DATE_EXPIRED_DEFAULT; 510 cms.setDateExpired(resource, newDateExpired, modifySubresources); 511 } 512 } finally { 513 cms.getRequestContext().removeAttribute(CmsResourceTypeXmlContent.ATTR_REVERSE_AVAILABILITY_MAPPING); 514 if (lockActionRecord.getChange() == LockChange.locked) { 515 try { 516 cms.unlockResource(resource); 517 } catch (CmsLockException e) { 518 LOG.warn(e.getLocalizedMessage(), e); 519 } 520 } 521 522 } 523 } 524 525 /** 526 * Creates a reset checkbox which can enable / disable a date field.<p> 527 * 528 * @param box the check box 529 * @param field the date field 530 */ 531 private void initResetCheckbox(CheckBox box, final CmsDateField field) { 532 533 box.addValueChangeListener(new ValueChangeListener() { 534 535 private static final long serialVersionUID = 1L; 536 537 public void valueChange(ValueChangeEvent event) { 538 539 Boolean value = (Boolean)(event.getProperty().getValue()); 540 if (value.booleanValue()) { 541 field.clear(); 542 field.setEnabled(false); 543 } else { 544 field.setEnabled(true); 545 } 546 } 547 }); 548 } 549 550 /** 551 * Performs the notification operations on a single resource.<p> 552 * 553 * @param cms the CMS context 554 * @param resName the VFS path of the resource 555 * @param enableNotification if the notification is activated 556 * @param notificationInterval the notification interval in days 557 * @param modifySiblings flag indicating to include resource siblings 558 * 559 * @throws CmsException if the availability and notification operations fail 560 */ 561 private void performSingleResourceNotification( 562 CmsObject cms, 563 String resName, 564 boolean enableNotification, 565 int notificationInterval, 566 boolean modifySiblings) 567 throws CmsException { 568 569 List<CmsResource> resources = new ArrayList<CmsResource>(); 570 if (modifySiblings) { 571 // modify all siblings of a resource 572 resources = cms.readSiblings(resName, CmsResourceFilter.IGNORE_EXPIRATION); 573 } else { 574 // modify only resource without siblings 575 resources.add(cms.readResource(resName, CmsResourceFilter.IGNORE_EXPIRATION)); 576 } 577 Iterator<CmsResource> i = resources.iterator(); 578 while (i.hasNext()) { 579 CmsResource resource = i.next(); 580 // lock resource if auto lock is enabled 581 CmsLockActionRecord lockRecord = CmsLockUtil.ensureLock(cms, resource); 582 try { 583 // write notification settings 584 writeProperty( 585 cms, 586 resource, 587 CmsPropertyDefinition.PROPERTY_NOTIFICATION_INTERVAL, 588 String.valueOf(notificationInterval)); 589 writeProperty( 590 cms, 591 resource, 592 CmsPropertyDefinition.PROPERTY_ENABLE_NOTIFICATION, 593 String.valueOf(enableNotification)); 594 } finally { 595 if (lockRecord.getChange() == LockChange.locked) { 596 cms.unlockResource(resource); 597 } 598 } 599 } 600 } 601 602 /** 603 * Writes a property value for a resource.<p> 604 * 605 * @param cms the cms context 606 * @param resource the path of the resource 607 * @param propertyName the name of the property 608 * @param propertyValue the new value of the property 609 * 610 * @throws CmsException if something goes wrong 611 */ 612 private void writeProperty(CmsObject cms, CmsResource resource, String propertyName, String propertyValue) 613 throws CmsException { 614 615 if (CmsStringUtil.isEmpty(propertyValue)) { 616 propertyValue = CmsProperty.DELETE_VALUE; 617 } 618 619 CmsProperty newProp = new CmsProperty(); 620 newProp.setName(propertyName); 621 CmsProperty oldProp = cms.readPropertyObject(resource, propertyName, false); 622 if (oldProp.isNullProperty()) { 623 // property value was not already set 624 if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) { 625 newProp.setStructureValue(propertyValue); 626 } else { 627 newProp.setResourceValue(propertyValue); 628 } 629 } else { 630 if (oldProp.getStructureValue() != null) { 631 newProp.setStructureValue(propertyValue); 632 newProp.setResourceValue(oldProp.getResourceValue()); 633 } else { 634 newProp.setResourceValue(propertyValue); 635 } 636 } 637 638 newProp.setAutoCreatePropertyDefinition(true); 639 640 String oldStructureValue = oldProp.getStructureValue(); 641 String newStructureValue = newProp.getStructureValue(); 642 if (CmsStringUtil.isEmpty(oldStructureValue)) { 643 oldStructureValue = CmsProperty.DELETE_VALUE; 644 } 645 if (CmsStringUtil.isEmpty(newStructureValue)) { 646 newStructureValue = CmsProperty.DELETE_VALUE; 647 } 648 649 String oldResourceValue = oldProp.getResourceValue(); 650 String newResourceValue = newProp.getResourceValue(); 651 if (CmsStringUtil.isEmpty(oldResourceValue)) { 652 oldResourceValue = CmsProperty.DELETE_VALUE; 653 } 654 if (CmsStringUtil.isEmpty(newResourceValue)) { 655 newResourceValue = CmsProperty.DELETE_VALUE; 656 } 657 658 // change property only if it has been changed 659 if (!oldResourceValue.equals(newResourceValue) || !oldStructureValue.equals(newStructureValue)) { 660 cms.writePropertyObjects(resource, Collections.singletonList(newProp)); 661 } 662 } 663}