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}