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.diff;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.types.CmsResourceTypeXmlContent;
034import org.opencms.file.types.CmsResourceTypeXmlPage;
035import org.opencms.file.types.I_CmsResourceType;
036import org.opencms.gwt.shared.CmsHistoryResourceBean;
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.Messages;
043import org.opencms.ui.dialogs.history.CmsHistoryDialog;
044import org.opencms.ui.util.table.CmsBeanTableBuilder;
045import org.opencms.util.CmsMacroResolver;
046import org.opencms.workplace.comparison.CmsElementComparison;
047import org.opencms.workplace.comparison.CmsXmlDocumentComparison;
048import org.opencms.xml.content.CmsXmlContent;
049import org.opencms.xml.content.CmsXmlContentFactory;
050
051import java.io.UnsupportedEncodingException;
052import java.util.List;
053
054import org.apache.commons.logging.Log;
055
056import com.google.common.base.Objects;
057import com.google.common.base.Optional;
058import com.google.common.collect.Lists;
059import com.vaadin.ui.Alignment;
060import com.vaadin.ui.Button;
061import com.vaadin.ui.Button.ClickEvent;
062import com.vaadin.ui.Button.ClickListener;
063import com.vaadin.ui.Component;
064import com.vaadin.ui.Panel;
065import com.vaadin.v7.ui.Table;
066import com.vaadin.v7.ui.VerticalLayout;
067
068/**
069 * Displays either a diff for the XML file, or a table displaying the differences between individual content values,
070 * allowing the user to switch between the two views.<p>
071 */
072public class CmsValueDiff implements I_CmsDiffProvider {
073
074    /** Logger instance for this class. */
075    private static final Log LOG = CmsLog.getLog(CmsValueDiff.class);
076
077    /**
078     * @see org.opencms.ui.dialogs.history.diff.I_CmsDiffProvider#diff(org.opencms.file.CmsObject, org.opencms.gwt.shared.CmsHistoryResourceBean, org.opencms.gwt.shared.CmsHistoryResourceBean)
079     */
080    public Optional<Component> diff(final CmsObject cms, CmsHistoryResourceBean v1, CmsHistoryResourceBean v2)
081    throws CmsException {
082
083        CmsResource resource1 = A_CmsAttributeDiff.readResource(cms, v1);
084        I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource1);
085        CmsMacroResolver resolver = new CmsVersionMacroResolver(v1, v2);
086        if ((type instanceof CmsResourceTypeXmlContent) || (type instanceof CmsResourceTypeXmlPage)) {
087            CmsResource resource2 = A_CmsAttributeDiff.readResource(cms, v2);
088            final Panel panel = new Panel(
089                CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_CONTENT_VALUE_TABLE_CAPTION_0));
090
091            final CmsFile file1 = cms.readFile(resource1);
092
093            final CmsFile file2 = cms.readFile(resource2);
094            VerticalLayout vl = new VerticalLayout();
095            vl.setMargin(true);
096            vl.setSpacing(true);
097            Table table = buildValueComparisonTable(cms, panel, file1, file2, resolver);
098            if (table.getContainerDataSource().size() == 0) {
099                return Optional.absent();
100            }
101            Button fileTextCompareButton = new Button(
102                CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_WHOLE_FILE_0));
103            vl.addComponent(fileTextCompareButton);
104            vl.setComponentAlignment(fileTextCompareButton, Alignment.MIDDLE_RIGHT);
105            fileTextCompareButton.addClickListener(new ClickListener() {
106
107                private static final long serialVersionUID = 1L;
108
109                @SuppressWarnings("synthetic-access")
110                public void buttonClick(ClickEvent event) {
111
112                    Component diffView = buildWholeFileDiffView(cms, file1, file2);
113                    CmsHistoryDialog.openChildDialog(
114                        panel,
115                        diffView,
116                        CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_WHOLE_FILE_0));
117                }
118            });
119            vl.addComponent(table);
120            panel.setContent(vl);
121            Component result = panel;
122            return Optional.fromNullable(result);
123        } else {
124            return Optional.absent();
125        }
126    }
127
128    /**
129     * Builds the table for the content value comparisons.<p>
130     *
131     * @param cms the CMS context
132     * @param parent the parent widget for the table (does not need to be the direct parent)
133     * @param file1 the first file
134     * @param file2 the second file
135     * @param macroResolver the macro resolver to use for building the table
136     *
137     * @return the table with the content value comparisons
138     *
139     * @throws CmsException if something goes wrong
140     */
141    private Table buildValueComparisonTable(
142        CmsObject cms,
143        final Component parent,
144        CmsFile file1,
145        CmsFile file2,
146        CmsMacroResolver macroResolver)
147    throws CmsException {
148
149        CmsXmlDocumentComparison comp = new CmsXmlDocumentComparison(cms, file1, file2);
150        CmsBeanTableBuilder<CmsValueCompareBean> builder = CmsBeanTableBuilder.newInstance(
151            CmsValueCompareBean.class,
152            A_CmsUI.get().getDisplayType().toString());
153        builder.setMacroResolver(macroResolver);
154
155        List<CmsValueCompareBean> rows = Lists.newArrayList();
156        for (CmsElementComparison entry : comp.getElements()) {
157            final String text1 = entry.getVersion1();
158            final String text2 = entry.getVersion2();
159            if (Objects.equal(text1, text2)) {
160                continue;
161            }
162            final CmsValueCompareBean row = new CmsValueCompareBean(cms, entry);
163            row.getChangeType().addClickListener(new ClickListener() {
164
165                private static final long serialVersionUID = 1L;
166
167                public void buttonClick(ClickEvent event) {
168
169                    CmsTextDiffPanel diffPanel = new CmsTextDiffPanel(text1, text2, true, true);
170                    diffPanel.setSizeFull();
171                    CmsHistoryDialog.openChildDialog(
172                        parent,
173                        diffPanel,
174                        CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_VALUE_1, row.getXPath()));
175                }
176
177            });
178            rows.add(row);
179        }
180        Table table = builder.buildTable(rows);
181        table.setSortEnabled(false);
182        table.setWidth("100%");
183        table.setPageLength(Math.min(rows.size(), 12));
184        return table;
185    }
186
187    /**
188     * Builds the diff view for the XML text.<p>
189     *
190     * @param cms the CMS context
191     * @param file1 the first file
192     * @param file2 the second file
193     *
194     * @return the diff view
195     */
196    private Component buildWholeFileDiffView(CmsObject cms, CmsFile file1, CmsFile file2) {
197
198        String encoding = "UTF-8";
199        try {
200            CmsXmlContent content1 = CmsXmlContentFactory.unmarshal(cms, file1);
201            encoding = content1.getEncoding();
202        } catch (CmsException e) {
203            String rootPath = file1.getRootPath();
204            LOG.error(
205                "Could not unmarshal file " + rootPath + " for determining encoding: " + e.getLocalizedMessage(),
206                e);
207        }
208        String text1 = decode(file1.getContents(), encoding);
209        String text2 = decode(file2.getContents(), encoding);
210        CmsTextDiffPanel diffPanel = new CmsTextDiffPanel(text1, text2, false, true);
211        return diffPanel;
212
213    }
214
215    /**
216     * Decodes the given data with the given encoding, falling back to the system encoding if necessary.<p>
217     *
218     * @param data the data to decode
219     * @param encoding the encoding to use
220     *
221     * @return if something goes wrong
222     */
223    private String decode(byte[] data, String encoding) {
224
225        try {
226            return new String(data, encoding);
227        } catch (UnsupportedEncodingException e) {
228            LOG.warn(e.getLocalizedMessage(), e);
229            return new String(data);
230        }
231    }
232
233}