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.history; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.gwt.CmsRpcException; 033import org.opencms.gwt.CmsVfsService; 034import org.opencms.gwt.shared.CmsHistoryResourceBean; 035import org.opencms.gwt.shared.CmsHistoryResourceCollection; 036import org.opencms.gwt.shared.CmsHistoryVersion.OfflineOnline; 037import org.opencms.main.CmsException; 038import org.opencms.main.CmsLog; 039import org.opencms.main.OpenCms; 040import org.opencms.ui.A_CmsUI; 041import org.opencms.ui.CmsVaadinUtils; 042import org.opencms.ui.I_CmsDialogContext; 043import org.opencms.ui.I_CmsUpdateListener; 044import org.opencms.ui.Messages; 045import org.opencms.ui.components.CmsBasicDialog; 046import org.opencms.ui.components.CmsConfirmationDialog; 047import org.opencms.ui.components.extensions.CmsGwtDialogExtension; 048import org.opencms.ui.dialogs.history.diff.CmsAttributeDiff; 049import org.opencms.ui.dialogs.history.diff.CmsImageDiff; 050import org.opencms.ui.dialogs.history.diff.CmsPropertyDiff; 051import org.opencms.ui.dialogs.history.diff.CmsShowVersionButtons; 052import org.opencms.ui.dialogs.history.diff.CmsTextDiff; 053import org.opencms.ui.dialogs.history.diff.CmsValueDiff; 054import org.opencms.ui.dialogs.history.diff.I_CmsDiffProvider; 055import org.opencms.ui.util.CmsComponentField; 056import org.opencms.ui.util.CmsLogicalCheckboxGroup; 057import org.opencms.ui.util.table.CmsBeanTableBuilder; 058import org.opencms.util.CmsUUID; 059 060import java.util.Arrays; 061import java.util.List; 062 063import org.apache.commons.logging.Log; 064 065import com.google.common.base.Optional; 066import com.google.common.collect.Lists; 067import com.vaadin.ui.Alignment; 068import com.vaadin.ui.Button; 069import com.vaadin.ui.Button.ClickEvent; 070import com.vaadin.ui.Button.ClickListener; 071import com.vaadin.v7.ui.CheckBox; 072import com.vaadin.ui.Component; 073import com.vaadin.v7.ui.HorizontalLayout; 074import com.vaadin.ui.Notification; 075import com.vaadin.ui.Panel; 076import com.vaadin.v7.ui.Table; 077import com.vaadin.v7.ui.VerticalLayout; 078import com.vaadin.ui.Window; 079import com.vaadin.ui.themes.ValoTheme; 080 081/** 082 * Dialog used to change resource modification times.<p> 083 */ 084public class CmsHistoryDialog extends CmsBasicDialog { 085 086 /** Logger for this class. */ 087 private static final Log LOG = CmsLog.getLog(CmsHistoryDialog.class); 088 089 /** Serial version id. */ 090 private static final long serialVersionUID = 1L; 091 092 /** The dialog context. */ 093 protected I_CmsDialogContext m_context; 094 095 /** Unbound field for the compare button. */ 096 private CmsComponentField<Button> m_compareButton = CmsComponentField.newInstance(); 097 098 /** Objects used to display differences between two versions. */ 099 private List<I_CmsDiffProvider> m_diffs = Arrays.<I_CmsDiffProvider> asList( 100 new CmsShowVersionButtons(), 101 new CmsPropertyDiff(), 102 new CmsAttributeDiff(), 103 new CmsImageDiff(), 104 new CmsTextDiff(), 105 new CmsValueDiff()); 106 107 /** Check box group for column V1.<p> */ 108 private CmsLogicalCheckboxGroup m_group1 = new CmsLogicalCheckboxGroup(); 109 110 /** Check box group for column V2.<p> */ 111 private CmsLogicalCheckboxGroup m_group2 = new CmsLogicalCheckboxGroup(); 112 113 /** Container for the list. */ 114 private VerticalLayout m_listContainer; 115 116 /** The resource for which the history dialog was opened. */ 117 private CmsResource m_resource; 118 119 /** Unbound field for the first selected check box. */ 120 private CmsComponentField<CheckBox> m_selected1 = CmsComponentField.newInstance(); 121 122 /** Unbound field for the second selected check box. */ 123 private CmsComponentField<CheckBox> m_selected2 = CmsComponentField.newInstance(); 124 125 /** 126 * Creates a new instance.<p> 127 * 128 * @param context the dialog context 129 */ 130 public CmsHistoryDialog(I_CmsDialogContext context) { 131 m_context = context; 132 m_resource = context.getResources().get(0); 133 setWidth("100%"); 134 CmsVaadinUtils.readAndLocalizeDesign( 135 this, 136 OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale()), 137 null); 138 CmsResource resource = context.getResources().get(0); 139 140 CmsVfsService vfsService = new CmsVfsService(); 141 vfsService.setCms(context.getCms()); 142 try { 143 CmsHistoryResourceCollection historyList = vfsService.getResourceHistoryInternal(resource.getStructureId()); 144 145 Table historyTable = buildHistoryTable(historyList); 146 147 historyTable.setWidth("100%"); 148 m_listContainer.setWidth("100%"); 149 Button compareButton = new Button(CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_0)); 150 m_listContainer.addComponent(compareButton); 151 m_listContainer.setComponentAlignment(compareButton, Alignment.MIDDLE_RIGHT); 152 153 compareButton.addClickListener(new ClickListener() { 154 155 private static final long serialVersionUID = 1L; 156 157 @SuppressWarnings("synthetic-access") 158 public void buttonClick(ClickEvent event) { 159 160 try { 161 tryCompare(); 162 } catch (Exception e) { 163 LOG.error(e.getLocalizedMessage(), e); 164 m_context.error(e); 165 } 166 } 167 }); 168 m_compareButton.set(compareButton); 169 m_group1.setChangeListener(new CmsLogicalCheckboxGroup.I_ChangeListener() { 170 171 @SuppressWarnings("synthetic-access") 172 public void onSelect(CheckBox box) { 173 174 m_selected1.set(box); 175 m_compareButton.get().setEnabled(canCompare(m_selected1.get(), m_selected2.get())); 176 177 } 178 }); 179 180 m_group2.setChangeListener(new CmsLogicalCheckboxGroup.I_ChangeListener() { 181 182 @SuppressWarnings("synthetic-access") 183 public void onSelect(CheckBox box) { 184 185 m_selected2.set(box); 186 m_compareButton.get().setEnabled(canCompare(m_selected1.get(), m_selected2.get())); 187 } 188 }); 189 m_compareButton.get().setEnabled(false); 190 m_listContainer.addComponent(historyTable); 191 } catch (CmsException e) { 192 LOG.error(e.getLocalizedMessage(), e); 193 } 194 addButton(createCloseButton()); 195 displayResourceInfo(m_context.getResources()); 196 } 197 198 /** 199 * Replaces the contents of the window containing a given component with a basic dialog 200 * consisting of a back button to restore the previous window state and another user provided widget.<p> 201 * 202 * @param currentComponent the component whose parent window's content should be replaced 203 * @param newView the user supplied part of the new window content 204 * @param newCaption the caption for the child dialog 205 */ 206 public static void openChildDialog(Component currentComponent, Component newView, String newCaption) { 207 208 final Window window = CmsVaadinUtils.getWindow(currentComponent); 209 final String oldCaption = window.getCaption(); 210 CmsBasicDialog dialog = new CmsBasicDialog(); 211 212 VerticalLayout vl = new VerticalLayout(); 213 dialog.setContent(vl); 214 Button backButton = new Button(CmsVaadinUtils.getMessageText(Messages.GUI_CHILD_DIALOG_GO_BACK_0)); 215 HorizontalLayout buttonBar = new HorizontalLayout(); 216 buttonBar.addComponent(backButton); 217 buttonBar.setMargin(true); 218 vl.addComponent(buttonBar); 219 vl.addComponent(newView); 220 final Component oldContent = window.getContent(); 221 if (oldContent instanceof CmsBasicDialog) { 222 List<CmsResource> infoResources = ((CmsBasicDialog)oldContent).getInfoResources(); 223 dialog.displayResourceInfo(infoResources); 224 if (oldContent instanceof CmsHistoryDialog) { 225 dialog.addButton(((CmsHistoryDialog)oldContent).createCloseButton()); 226 } 227 } 228 backButton.addClickListener(new ClickListener() { 229 230 private static final long serialVersionUID = 1L; 231 232 public void buttonClick(ClickEvent event) { 233 234 window.setContent(oldContent); 235 window.setCaption(oldCaption); 236 window.center(); 237 238 } 239 240 }); 241 window.setContent(dialog); 242 window.setCaption(newCaption); 243 window.center(); 244 245 } 246 247 /** 248 * Restores a resource's state to the given version, but asks the user for confirmation beforehand.<p> 249 * 250 * @param cms the CMS context 251 * @param structureId the structure id of the resource to restore 252 * @param version the version to which the resource should be restored 253 */ 254 public void actionRestore(final CmsObject cms, final CmsUUID structureId, final Integer version) { 255 256 String title = CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_CONFIRM_RESTORE_TITLE_0); 257 String message = CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_CONFIRM_RESTORE_0); 258 259 CmsConfirmationDialog.show(title, message, new Runnable() { 260 261 @SuppressWarnings("synthetic-access") 262 public void run() { 263 264 CmsVfsService svc = new CmsVfsService(); 265 svc.setCms(cms); 266 try { 267 svc.restoreResource(structureId, version.intValue()); 268 m_context.finish(Arrays.asList(m_resource.getStructureId())); 269 } catch (CmsRpcException e) { 270 LOG.error(e.getLocalizedMessage(), e); 271 m_context.error(e); 272 } 273 } 274 }); 275 } 276 277 /** 278 * Creates a close button for child dialogs.<p> 279 * 280 * @return the close button 281 */ 282 public Button createCloseButton() { 283 284 Button button = new Button(CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_BUTTON_CLOSE_DIALOG_0)); 285 button.setWidth("150px"); 286 button.addClickListener(new ClickListener() { 287 288 private static final long serialVersionUID = 1L; 289 290 public void buttonClick(ClickEvent event) { 291 292 m_context.finish(Lists.newArrayList(m_context.getResources().get(0).getStructureId())); 293 } 294 }); 295 return button; 296 } 297 298 /** 299 * Opens the 'compare' view for the two selected versions of the resource.<p> 300 * 301 * @throws CmsException if something goes wrong 302 */ 303 public void tryCompare() throws CmsException { 304 305 CmsObject cms = A_CmsUI.getCmsObject(); 306 CheckBox check1 = m_group1.getSelected(); 307 CheckBox check2 = m_group2.getSelected(); 308 if (!canCompare(check1, check2)) { 309 Notification.show( 310 CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_SELECT_TWO_DIFFERENT_VERSIONS_0)); 311 } else { 312 CmsHistoryResourceBean bean1 = (CmsHistoryResourceBean)(check1.getData()); 313 CmsHistoryResourceBean bean2 = (CmsHistoryResourceBean)(check2.getData()); 314 VerticalLayout diffContainer = new VerticalLayout(); 315 diffContainer.setSpacing(true); 316 for (I_CmsDiffProvider diff : m_diffs) { 317 Optional<Component> optionalDiff = diff.diff(cms, bean1, bean2); 318 if (optionalDiff.isPresent()) { 319 diffContainer.addComponent(optionalDiff.get()); 320 } 321 } 322 Panel panel = new Panel(); 323 panel.setSizeFull(); 324 diffContainer.setWidth("100%"); 325 diffContainer.setMargin(true); 326 panel.addStyleName(ValoTheme.PANEL_BORDERLESS); 327 panel.setContent(diffContainer); 328 openChildDialog( 329 CmsHistoryDialog.this, 330 panel, 331 CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_0)); 332 } 333 334 } 335 336 /** 337 * Displays the preview for a given resource version.<p> 338 * 339 * @param bean the resource version to display the preview for 340 */ 341 private void actionPreview(CmsHistoryResourceBean bean) { 342 343 CmsGwtDialogExtension ext = new CmsGwtDialogExtension(A_CmsUI.get(), new I_CmsUpdateListener<String>() { 344 345 public void onUpdate(List<String> updatedItems) { 346 347 // nothing to do 348 } 349 }); 350 OfflineOnline offOnline = null; 351 if (bean.getVersion().isOffline()) { 352 offOnline = OfflineOnline.offline; 353 } 354 if (bean.getVersion().isOnline()) { 355 offOnline = OfflineOnline.online; 356 } 357 ext.showPreview(bean.getStructureId(), bean.getVersion().getVersionNumber(), offOnline); 358 } 359 360 /** 361 * Builds a table containing the different versions of a resource.<p> 362 * 363 * @param historyList the list of history resource beans from which to construct the table 364 * 365 * @return the table 366 */ 367 private Table buildHistoryTable(CmsHistoryResourceCollection historyList) { 368 369 final CmsObject cms = A_CmsUI.getCmsObject(); 370 try { 371 CmsBeanTableBuilder<CmsHistoryRow> builder = CmsBeanTableBuilder.newInstance( 372 CmsHistoryRow.class, 373 A_CmsUI.get().getDisplayType().toString()); 374 List<CmsHistoryRow> rows = Lists.newArrayList(); 375 376 for (CmsHistoryResourceBean bean : historyList.getResources()) { 377 final CmsHistoryResourceBean beanFinal = bean; 378 final CmsUUID structureId = bean.getStructureId(); 379 CmsHistoryRow row = new CmsHistoryRow(bean); 380 rows.add(row); 381 m_group1.add(row.getCheckBoxV1()); 382 m_group2.add(row.getCheckBoxV2()); 383 final Integer version = bean.getVersion().getVersionNumber(); 384 if (version != null) { 385 row.getRestoreButton().addClickListener(new ClickListener() { 386 387 private static final long serialVersionUID = 1L; 388 389 public void buttonClick(ClickEvent event) { 390 391 actionRestore(cms, structureId, version); 392 393 } 394 395 }); 396 } 397 row.getPreviewButton().addClickListener(new ClickListener() { 398 399 private static final long serialVersionUID = 1L; 400 401 @SuppressWarnings("synthetic-access") 402 public void buttonClick(ClickEvent event) { 403 404 actionPreview(beanFinal); 405 406 } 407 408 }); 409 for (CheckBox checkBox : Arrays.asList(row.getCheckBoxV1(), row.getCheckBoxV2())) { 410 checkBox.setData(bean); 411 } 412 } 413 414 Table result = builder.buildTable(rows); 415 result.setPageLength(Math.min(rows.size(), 12)); 416 result.setSortEnabled(false); 417 return result; 418 } catch (Exception e) { 419 return null; 420 421 } 422 } 423 424 /** 425 * Checks if two different versions are selected, and so can be compared.<p> 426 * 427 * @param check1 the first selected check box 428 * @param check2 the second selected check box 429 * 430 * @return true if the check boxes correspond to two different versions 431 */ 432 private boolean canCompare(CheckBox check1, CheckBox check2) { 433 434 return !((check1 == null) || (check2 == null) || (check1.getData() == check2.getData())); 435 } 436 437}