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.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                    CmsResource source1 = relation.getSource(cms, CmsResourceFilter.ALL);
255                    if (!source1.getState().isDeleted()) {
256                        result1.add(source1);
257                    }
258                }
259            }
260            List<CmsResource> linkSources = result1;
261            for (CmsResource source : linkSources) {
262                if (reverse) {
263                    linkMap.put(resource, source);
264                } else {
265                    linkMap.put(source, resource);
266                }
267            }
268        }
269        return linkMap;
270
271    }
272
273    /**
274     * Cancels the dialog.<p>
275     */
276    void cancel() {
277
278        m_context.finish(new ArrayList<CmsUUID>());
279    }
280
281    /**
282     * Displays the broken links.<p>
283     */
284    void displayBrokenLinks() {
285
286        I_CmsGwtContextMenuServerRpc rpc = new I_CmsGwtContextMenuServerRpc() {
287
288            public void refresh(String id) {
289
290                if (id != null) {
291                    m_context.finish(Arrays.asList(new CmsUUID(id)));
292                } else {
293                    m_context.finish(Collections.emptyList());
294                }
295            }
296        };
297        CmsObject cms = A_CmsUI.getCmsObject();
298        m_resourceBox.removeAllComponents();
299        m_resourceBox.addStyleName("o-broken-links");
300        m_deleteResource.setVisible(false);
301        m_okButton.setVisible(true);
302        boolean canIgnoreBrokenLinks = OpenCms.getWorkplaceManager().getDefaultUserSettings().isAllowBrokenRelations()
303            || OpenCms.getRoleManager().hasRole(cms, CmsRole.VFS_MANAGER);
304        try {
305            Multimap<CmsResource, CmsResource> brokenLinks = getBrokenLinks(
306                cms,
307                m_context.getResources(),
308                CmsResource.DELETE_REMOVE_SIBLINGS.equals(m_deleteSiblings.getValue()));
309            if (brokenLinks.isEmpty()) {
310                m_linksLabel.setVisible(false);
311                String noLinksBroken = CmsVaadinUtils.getMessageText(
312                    org.opencms.workplace.commons.Messages.GUI_DELETE_RELATIONS_NOT_BROKEN_0);
313                m_resourceBox.addComponent(new Label(noLinksBroken));
314            } else {
315                if (!canIgnoreBrokenLinks) {
316                    m_deleteResource.setVisible(true);
317                    m_deleteResource.setValue(
318                        CmsVaadinUtils.getMessageText(
319                            org.opencms.workplace.commons.Messages.GUI_DELETE_RELATIONS_NOT_ALLOWED_0));
320                    m_okButton.setVisible(false);
321                }
322                for (CmsResource source : brokenLinks.keySet()) {
323                    CmsResourceInfo parentInfo = new CmsResourceInfo(source);
324                    CmsGwtContextMenuButton contextMenu = new CmsGwtContextMenuButton(source.getStructureId(), rpc);
325                    contextMenu.addStyleName("o-gwt-contextmenu-button-margin");
326                    parentInfo.setButtonWidget(contextMenu);
327                    m_resourceBox.addComponent(parentInfo);
328                    for (CmsResource target : brokenLinks.get(source)) {
329                        CmsResourceInfo childInfo = new CmsResourceInfo(target);
330                        childInfo.addStyleName("o-deleted");
331                        m_resourceBox.addComponent(indent(childInfo));
332                    }
333
334                }
335            }
336        } catch (CmsException e) {
337            m_context.error(e);
338            return;
339        }
340    }
341
342    /**
343     * Submits the dialog.<p>
344     */
345    void submit() {
346
347        CmsObject cms = A_CmsUI.getCmsObject();
348        try {
349            List<CmsUUID> changedIds = Lists.newArrayList();
350            CmsResourceDeleteMode mode = (CmsResourceDeleteMode)m_deleteSiblings.getValue();
351            for (CmsResource resource : m_context.getResources()) {
352                if (resource.getState().isDeleted()) {
353                    continue;
354                }
355                changedIds.add(resource.getStructureId());
356                CmsLockActionRecord lockRecord = CmsLockUtil.ensureLock(cms, resource);
357                try {
358                    cms.deleteResource(cms.getSitePath(resource), mode);
359                } finally {
360                    if (lockRecord.getChange().equals(LockChange.locked)) {
361                        if (!resource.getState().isNew()) {
362                            try {
363                                cms.unlockResource(resource);
364                            } catch (CmsVfsResourceNotFoundException e) {
365                                LOG.warn(e.getLocalizedMessage(), e);
366                            } catch (CmsLockException e) {
367                                LOG.warn(e.getLocalizedMessage(), e);
368                            }
369                        }
370                    }
371                }
372            }
373            m_context.finish(changedIds);
374        } catch (Exception e) {
375            m_context.error(e);
376        }
377    }
378
379    /**
380     * Checks whether the selected resources have siblings.<p>
381     *
382     * @return whether the selected resources have siblings
383     */
384    private boolean hasSiblings() {
385
386        for (CmsResource res : m_context.getResources()) {
387            if (res.getSiblingCount() > 1) {
388                return true;
389            }
390        }
391        return false;
392    }
393
394    /**
395     * Indents a resources box.<p>
396     *
397     * @param resourceInfo the resource box
398     *
399     * @return an indented resource box
400     */
401    private Component indent(CmsResourceInfo resourceInfo) {
402
403        boolean simple = false;
404
405        if (simple) {
406            return resourceInfo;
407
408        } else {
409            HorizontalLayout hl = new HorizontalLayout();
410            Label label = new Label("");
411            label.setWidth("35px");
412            hl.addComponent(label);
413            hl.addComponent(resourceInfo);
414            hl.setExpandRatio(resourceInfo, 1.0f);
415            hl.setWidth("100%");
416            return hl;
417        }
418    }
419
420}