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}