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}