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}