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}