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;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResource.CmsResourceDeleteMode;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.file.CmsVfsResourceNotFoundException;
035import org.opencms.lock.CmsLockActionRecord;
036import org.opencms.lock.CmsLockActionRecord.LockChange;
037import org.opencms.lock.CmsLockException;
038import org.opencms.lock.CmsLockUtil;
039import org.opencms.main.CmsException;
040import org.opencms.main.CmsLog;
041import org.opencms.main.OpenCms;
042import org.opencms.relations.CmsRelation;
043import org.opencms.relations.CmsRelationFilter;
044import org.opencms.security.CmsRole;
045import org.opencms.ui.A_CmsUI;
046import org.opencms.ui.CmsVaadinUtils;
047import org.opencms.ui.I_CmsDialogContext;
048import org.opencms.ui.components.CmsBasicDialog;
049import org.opencms.ui.components.CmsOkCancelActionHandler;
050import org.opencms.ui.components.CmsResourceInfo;
051import org.opencms.util.CmsUUID;
052import org.opencms.workplace.commons.Messages;
053
054import java.util.ArrayList;
055import java.util.HashSet;
056import java.util.List;
057import java.util.Set;
058
059import org.apache.commons.logging.Log;
060
061import com.google.common.collect.HashMultimap;
062import com.google.common.collect.Lists;
063import com.google.common.collect.Multimap;
064import com.vaadin.ui.Button;
065import com.vaadin.ui.Button.ClickListener;
066import com.vaadin.ui.Component;
067import com.vaadin.v7.data.Property.ValueChangeEvent;
068import com.vaadin.v7.data.Property.ValueChangeListener;
069import com.vaadin.v7.ui.HorizontalLayout;
070import com.vaadin.v7.ui.Label;
071import com.vaadin.v7.ui.OptionGroup;
072import com.vaadin.v7.ui.VerticalLayout;
073
074/**
075 * Dialog for deleting resources.<p>
076 */
077public class CmsDeleteDialog extends CmsBasicDialog {
078
079    /** Logger instance for this class. */
080    static final Log LOG = CmsLog.getLog(CmsDeleteDialog.class);
081
082    /** Serial version id. */
083    private static final long serialVersionUID = 1L;
084
085    /** Box for displaying resource widgets. */
086    private VerticalLayout m_resourceBox;
087
088    // private AbstractComponent m_container;
089
090    /** Message if deleting resources is allowed or not. */
091    private Label m_deleteResource;
092
093    /** Label for the links. */
094    private Label m_linksLabel;
095
096    /** The OK button. */
097    private Button m_okButton;
098
099    /** The cancel button. */
100    private Button m_cancelButton;
101
102    /** The dialog context. */
103    private I_CmsDialogContext m_context;
104
105    /** The delete siblings check box group. */
106    private OptionGroup m_deleteSiblings;
107
108    /**
109     * Creates a new instance.<p>
110     *
111     * @param context the dialog context
112     */
113    public CmsDeleteDialog(I_CmsDialogContext context) {
114
115        m_context = context;
116        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null);
117        m_deleteSiblings.addItem(CmsResource.DELETE_PRESERVE_SIBLINGS);
118        m_deleteSiblings.setItemCaption(
119            CmsResource.DELETE_PRESERVE_SIBLINGS,
120            CmsVaadinUtils.getMessageText(Messages.GUI_DELETE_PRESERVE_SIBLINGS_0));
121        m_deleteSiblings.addItem(CmsResource.DELETE_REMOVE_SIBLINGS);
122        m_deleteSiblings.setItemCaption(
123            CmsResource.DELETE_REMOVE_SIBLINGS,
124            CmsVaadinUtils.getMessageText(Messages.GUI_DELETE_ALL_SIBLINGS_0));
125        m_deleteSiblings.setValue(CmsResource.DELETE_PRESERVE_SIBLINGS);
126        m_deleteSiblings.addValueChangeListener(new ValueChangeListener() {
127
128            private static final long serialVersionUID = 1L;
129
130            public void valueChange(ValueChangeEvent event) {
131
132                displayBrokenLinks();
133            }
134        });
135        m_deleteSiblings.setVisible(hasSiblings());
136        displayResourceInfo(m_context.getResources());
137
138        m_cancelButton.addClickListener(new ClickListener() {
139
140            /** Serial version id. */
141            private static final long serialVersionUID = 1L;
142
143            public void buttonClick(Button.ClickEvent event) {
144
145                cancel();
146            }
147        });
148
149        m_okButton.addClickListener(new ClickListener() {
150
151            /** Serial version id. */
152            private static final long serialVersionUID = 1L;
153
154            public void buttonClick(Button.ClickEvent event) {
155
156                submit();
157            }
158        });
159
160        displayBrokenLinks();
161
162        setActionHandler(new CmsOkCancelActionHandler() {
163
164            private static final long serialVersionUID = 1L;
165
166            @Override
167            protected void cancel() {
168
169                CmsDeleteDialog.this.cancel();
170            }
171
172            @Override
173            protected void ok() {
174
175                submit();
176            }
177        });
178    }
179
180    /**
181     * Gets the broken links.<p>
182     *
183     * @param cms the CMS context
184     * @param selectedResources the selected resources
185     * @param includeSiblings <code>true</code> if siblings would be deleted too
186     *
187     * @return multimap of broken links, with sources as keys and targets as values
188     *
189     * @throws CmsException if something goes wrong
190     */
191    public static Multimap<CmsResource, CmsResource> getBrokenLinks(
192        CmsObject cms,
193        List<CmsResource> selectedResources,
194        boolean includeSiblings)
195    throws CmsException {
196
197        return getBrokenLinks(cms, selectedResources, includeSiblings, false);
198
199    }
200
201    /**
202     * Gets the broken links.<p>
203     *
204     * @param cms the CMS context
205     * @param selectedResources the selected resources
206     * @param includeSiblings <code>true</code> if siblings would be deleted too
207     * @param reverse <code>true</code> if the resulting map should be reverted
208     *
209     * @return multimap of broken links, with sources as keys and targets as values
210     *
211     * @throws CmsException if something goes wrong
212     */
213    public static Multimap<CmsResource, CmsResource> getBrokenLinks(
214        CmsObject cms,
215        List<CmsResource> selectedResources,
216        boolean includeSiblings,
217        boolean reverse)
218    throws CmsException {
219
220        Set<CmsResource> descendants = new HashSet<CmsResource>();
221        for (CmsResource root : selectedResources) {
222            descendants.add(root);
223            if (root.isFolder()) {
224                descendants.addAll(cms.readResources(cms.getSitePath(root), CmsResourceFilter.IGNORE_EXPIRATION));
225            }
226        }
227
228        if (includeSiblings) {
229            // add siblings
230            for (CmsResource res : new HashSet<CmsResource>(descendants)) {
231                if (res.isFile()) {
232                    descendants.addAll(cms.readSiblings(res, CmsResourceFilter.IGNORE_EXPIRATION));
233                }
234            }
235        }
236        HashSet<CmsUUID> deleteIds = new HashSet<CmsUUID>();
237        for (CmsResource deleteRes : descendants) {
238            deleteIds.add(deleteRes.getStructureId());
239        }
240        Multimap<CmsResource, CmsResource> linkMap = HashMultimap.create();
241        for (CmsResource resource : descendants) {
242            List<CmsRelation> relations = cms.getRelationsForResource(resource, CmsRelationFilter.SOURCES);
243            List<CmsResource> result1 = new ArrayList<CmsResource>();
244            for (CmsRelation relation : relations) {
245                // only add related resources that are not going to be deleted
246                if (!deleteIds.contains(relation.getSourceId())) {
247                    CmsResource source1 = relation.getSource(cms, CmsResourceFilter.ALL);
248                    if (!source1.getState().isDeleted()) {
249                        result1.add(source1);
250                    }
251                }
252            }
253            List<CmsResource> linkSources = result1;
254            for (CmsResource source : linkSources) {
255                if (reverse) {
256                    linkMap.put(resource, source);
257                } else {
258                    linkMap.put(source, resource);
259                }
260            }
261        }
262        return linkMap;
263
264    }
265
266    /**
267     * Cancels the dialog.<p>
268     */
269    void cancel() {
270
271        m_context.finish(new ArrayList<CmsUUID>());
272    }
273
274    /**
275     * Displays the broken links.<p>
276     */
277    void displayBrokenLinks() {
278
279        CmsObject cms = A_CmsUI.getCmsObject();
280        m_resourceBox.removeAllComponents();
281        m_deleteResource.setValue(
282            CmsVaadinUtils.getMessageText(org.opencms.workplace.commons.Messages.GUI_DELETE_MULTI_CONFIRMATION_0));
283        m_okButton.setVisible(true);
284        boolean canIgnoreBrokenLinks = OpenCms.getWorkplaceManager().getDefaultUserSettings().isAllowBrokenRelations()
285            || OpenCms.getRoleManager().hasRole(cms, CmsRole.VFS_MANAGER);
286        try {
287            Multimap<CmsResource, CmsResource> brokenLinks = getBrokenLinks(
288                cms,
289                m_context.getResources(),
290                CmsResource.DELETE_REMOVE_SIBLINGS.equals(m_deleteSiblings.getValue()));
291            if (brokenLinks.isEmpty()) {
292                m_linksLabel.setVisible(false);
293                String noLinksBroken = CmsVaadinUtils.getMessageText(
294                    org.opencms.workplace.commons.Messages.GUI_DELETE_RELATIONS_NOT_BROKEN_0);
295                m_resourceBox.addComponent(new Label(noLinksBroken));
296            } else {
297                if (!canIgnoreBrokenLinks) {
298                    m_deleteResource.setValue(
299                        CmsVaadinUtils.getMessageText(
300                            org.opencms.workplace.commons.Messages.GUI_DELETE_RELATIONS_NOT_ALLOWED_0));
301                    m_okButton.setVisible(false);
302                }
303                for (CmsResource source : brokenLinks.keySet()) {
304                    m_resourceBox.addComponent(new CmsResourceInfo(source));
305                    for (CmsResource target : brokenLinks.get(source)) {
306                        m_resourceBox.addComponent(indent(new CmsResourceInfo(target)));
307                    }
308
309                }
310            }
311        } catch (CmsException e) {
312            m_context.error(e);
313            return;
314        }
315    }
316
317    /**
318     * Submits the dialog.<p>
319     */
320    void submit() {
321
322        CmsObject cms = A_CmsUI.getCmsObject();
323        try {
324            List<CmsUUID> changedIds = Lists.newArrayList();
325            CmsResourceDeleteMode mode = (CmsResourceDeleteMode)m_deleteSiblings.getValue();
326            for (CmsResource resource : m_context.getResources()) {
327                changedIds.add(resource.getStructureId());
328                CmsLockActionRecord lockRecord = CmsLockUtil.ensureLock(cms, resource);
329                try {
330                    cms.deleteResource(cms.getSitePath(resource), mode);
331                } finally {
332                    if (lockRecord.getChange().equals(LockChange.locked)) {
333                        if (!resource.getState().isNew()) {
334                            try {
335                                cms.unlockResource(resource);
336                            } catch (CmsVfsResourceNotFoundException e) {
337                                LOG.warn(e.getLocalizedMessage(), e);
338                            } catch (CmsLockException e) {
339                                LOG.warn(e.getLocalizedMessage(), e);
340                            }
341                        }
342                    }
343                }
344            }
345            m_context.finish(changedIds);
346        } catch (Exception e) {
347            m_context.error(e);
348        }
349    }
350
351    /**
352     * Checks whether the selected resources have siblings.<p>
353     *
354     * @return whether the selected resources have siblings
355     */
356    private boolean hasSiblings() {
357
358        for (CmsResource res : m_context.getResources()) {
359            if (res.getSiblingCount() > 1) {
360                return true;
361            }
362        }
363        return false;
364    }
365
366    /**
367     * Indents a resources box.<p>
368     *
369     * @param resourceInfo the resource box
370     *
371     * @return an indented resource box
372     */
373    private Component indent(CmsResourceInfo resourceInfo) {
374
375        boolean simple = false;
376
377        if (simple) {
378            return resourceInfo;
379
380        } else {
381            HorizontalLayout hl = new HorizontalLayout();
382            Label label = new Label("");
383            label.setWidth("35px");
384            hl.addComponent(label);
385            hl.addComponent(resourceInfo);
386            hl.setExpandRatio(resourceInfo, 1.0f);
387            hl.setWidth("100%");
388            return hl;
389        }
390    }
391
392}