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}