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