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            Button fileTextCompareButton = new Button(
099                CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_WHOLE_FILE_0));
100            vl.addComponent(fileTextCompareButton);
101            vl.setComponentAlignment(fileTextCompareButton, Alignment.MIDDLE_RIGHT);
102            fileTextCompareButton.addClickListener(new ClickListener() {
103
104                private static final long serialVersionUID = 1L;
105
106                @SuppressWarnings("synthetic-access")
107                public void buttonClick(ClickEvent event) {
108
109                    Component diffView = buildWholeFileDiffView(cms, file1, file2);
110                    CmsHistoryDialog.openChildDialog(
111                        panel,
112                        diffView,
113                        CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_WHOLE_FILE_0));
114                }
115            });
116            vl.addComponent(table);
117            panel.setContent(vl);
118            Component result = panel;
119            return Optional.fromNullable(result);
120        } else {
121            return Optional.absent();
122        }
123    }
124
125    /**
126     * Builds the table for the content value comparisons.<p>
127     *
128     * @param cms the CMS context
129     * @param parent the parent widget for the table (does not need to be the direct parent)
130     * @param file1 the first file
131     * @param file2 the second file
132     * @param macroResolver the macro resolver to use for building the table
133     *
134     * @return the table with the content value comparisons
135     *
136     * @throws CmsException if something goes wrong
137     */
138    private Table buildValueComparisonTable(
139        CmsObject cms,
140        final Component parent,
141        CmsFile file1,
142        CmsFile file2,
143        CmsMacroResolver macroResolver)
144    throws CmsException {
145
146        CmsXmlDocumentComparison comp = new CmsXmlDocumentComparison(cms, file1, file2);
147        CmsBeanTableBuilder<CmsValueCompareBean> builder = CmsBeanTableBuilder.newInstance(
148            CmsValueCompareBean.class,
149            A_CmsUI.get().getDisplayType().toString());
150        builder.setMacroResolver(macroResolver);
151
152        List<CmsValueCompareBean> rows = Lists.newArrayList();
153        for (CmsElementComparison entry : comp.getElements()) {
154            final String text1 = entry.getVersion1();
155            final String text2 = entry.getVersion2();
156            if (Objects.equal(text1, text2)) {
157                continue;
158            }
159            final CmsValueCompareBean row = new CmsValueCompareBean(cms, entry);
160            row.getChangeType().addClickListener(new ClickListener() {
161
162                private static final long serialVersionUID = 1L;
163
164                public void buttonClick(ClickEvent event) {
165
166                    CmsTextDiffPanel diffPanel = new CmsTextDiffPanel(text1, text2, true, true);
167                    diffPanel.setSizeFull();
168                    CmsHistoryDialog.openChildDialog(
169                        parent,
170                        diffPanel,
171                        CmsVaadinUtils.getMessageText(Messages.GUI_HISTORY_DIALOG_COMPARE_VALUE_1, row.getXPath()));
172                }
173
174            });
175            rows.add(row);
176        }
177        Table table = builder.buildTable(rows);
178        table.setSortEnabled(false);
179        table.setWidth("100%");
180        table.setPageLength(Math.min(rows.size(), 12));
181        return table;
182    }
183
184    /**
185     * Builds the diff view for the XML text.<p>
186     *
187     * @param cms the CMS context
188     * @param file1 the first file
189     * @param file2 the second file
190     *
191     * @return the diff view
192     */
193    private Component buildWholeFileDiffView(CmsObject cms, CmsFile file1, CmsFile file2) {
194
195        String encoding = "UTF-8";
196        try {
197            CmsXmlContent content1 = CmsXmlContentFactory.unmarshal(cms, file1);
198            encoding = content1.getEncoding();
199        } catch (CmsException e) {
200            String rootPath = file1.getRootPath();
201            LOG.error(
202                "Could not unmarshal file " + rootPath + " for determining encoding: " + e.getLocalizedMessage(),
203                e);
204        }
205        String text1 = decode(file1.getContents(), encoding);
206        String text2 = decode(file2.getContents(), encoding);
207        CmsTextDiffPanel diffPanel = new CmsTextDiffPanel(text1, text2, false, true);
208        return diffPanel;
209
210    }
211
212    /**
213     * Decodes the given data with the given encoding, falling back to the system encoding if necessary.<p>
214     *
215     * @param data the data to decode
216     * @param encoding the encoding to use
217     *
218     * @return if something goes wrong
219     */
220    private String decode(byte[] data, String encoding) {
221
222        try {
223            return new String(data, encoding);
224        } catch (UnsupportedEncodingException e) {
225            LOG.warn(e.getLocalizedMessage(), e);
226            return new String(data);
227        }
228    }
229
230}