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