001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.content.version; 017 018import java.io.IOException; 019import java.net.URISyntaxException; 020import java.nio.charset.StandardCharsets; 021import java.util.Collection; 022import java.util.List; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.cocoon.ProcessingException; 031import org.apache.commons.io.IOUtils; 032import org.apache.excalibur.source.Source; 033import org.apache.excalibur.source.SourceResolver; 034import org.apache.http.client.utils.URIBuilder; 035 036import org.ametys.cms.content.compare.ContentComparatorChange; 037import org.ametys.cms.content.compare.ContentComparatorResult; 038import org.ametys.cms.contenttype.ContentTypesHelper; 039import org.ametys.cms.repository.Content; 040import org.ametys.cms.rights.ContentRightAssignmentContext; 041import org.ametys.core.ui.Callable; 042import org.ametys.core.ui.ClientSideElement; 043import org.ametys.core.ui.StaticClientSideElement; 044import org.ametys.plugins.repository.AmetysRepositoryException; 045import org.ametys.plugins.repository.data.external.ExternalizableDataProviderExtensionPoint; 046import org.ametys.runtime.model.View; 047import org.ametys.runtime.model.ViewHelper; 048 049/** 050 * {@link ClientSideElement} for the tool for comparing a content between a base version and a target version. 051 */ 052public class CompareContentVersionToolClientSideElement extends StaticClientSideElement 053{ 054 private CompareVersionHelper _compareVersionHelper; 055 private SourceResolver _sourceResolver; 056 private ContentTypesHelper _cTypesHelper; 057 private ExternalizableDataProviderExtensionPoint _externalizableDataProviderEP; 058 059 @Override 060 public void service(ServiceManager smanager) throws ServiceException 061 { 062 super.service(smanager); 063 _compareVersionHelper = (CompareVersionHelper) smanager.lookup(CompareVersionHelper.ROLE); 064 _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 065 _cTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 066 _externalizableDataProviderEP = (ExternalizableDataProviderExtensionPoint) smanager.lookup(ExternalizableDataProviderExtensionPoint.ROLE); 067 } 068 069 /** 070 * Gets the JSON information about the changes for the given content id between the base and target provided versions. 071 * @param contentId The {@link Content} id 072 * @param viewName The view to use 073 * @param showAllData true to display all data, false to display only data with diff 074 * @param targetVersion The content version to be compared 075 * @param baseVersion The base content version 076 * @return The information 077 * @throws IOException If an I/O exception occurred during the comparison between the two versions, or when getting content values 078 * @throws ProcessingException If an exception occurred when converting the "change" view 079 */ 080 @Callable(rights = "CMS_Rights_Content_History", paramIndex = 0, rightContext = ContentRightAssignmentContext.ID) 081 public Map<String, Object> getDiffValues(String contentId, String viewName, boolean showAllData, String targetVersion, String baseVersion) throws IOException, ProcessingException 082 { 083 ContentVersionComparator comparator = new ContentVersionComparator(contentId, viewName, showAllData, targetVersion, baseVersion); 084 comparator.compare(); 085 return comparator.resultToJson(); 086 } 087 088 private class ContentVersionComparator 089 { 090 private final String _contentId; 091 private final String _baseVersion; 092 private final String _targetVersion; 093 private Content _targetContent; 094 private ContentComparatorResult _comparatorResult; 095 private CompareView _resultCompareView; 096 private String _viewName; 097 private boolean _showAllData; 098 099 ContentVersionComparator(String contentId, String viewName, boolean showAllData, String version, String baseVersion) 100 { 101 _contentId = contentId; 102 _viewName = viewName; 103 _showAllData = showAllData; 104 _baseVersion = baseVersion; 105 _targetVersion = version; 106 } 107 108 void compare() throws IOException 109 { 110 _targetContent = _compareVersionHelper.getContentVersion(_contentId, _targetVersion); 111 112 _comparatorResult = _compareVersionHelper.compareVersions(_contentId, _targetVersion, _baseVersion); 113 114 View wrappedEditionView = ViewHelper.getTruncatedView(_retrieveEditionView()); 115 Set<String> externalizableDataPaths = _externalizableDataProviderEP.getExternalizableDataPaths(_targetContent); 116 _resultCompareView = new CompareView(wrappedEditionView, externalizableDataPaths, _showAllData, _comparatorResult); 117 } 118 119 Map<String, Object> resultToJson() throws IOException, ProcessingException 120 { 121 return Map.of( 122 "view", _resultCompareViewToJson(), 123 "changedAttributeDataPaths", _changedAttributeDataPaths(), 124 // FIXME REPOSITORY-454 Use "baseValues", _contentToJson(_baseContent), 125 "baseValues", _saxedContent(_baseVersion), 126 // FIXME REPOSITORY-454 Use "values", _contentToJson(_targetContent)); 127 "values", _saxedContent(_targetVersion)); 128 } 129 130 private Map<String, Object> _resultCompareViewToJson() throws ProcessingException 131 { 132 return _resultCompareView.toJSON(); 133 134 } 135 136 private Collection<String> _changedAttributeDataPaths() throws AmetysRepositoryException 137 { 138 List<ContentComparatorChange> contentComparatorChanges = _comparatorResult.getChanges(); 139 return _compareVersionHelper.filterChanges(contentComparatorChanges) 140 .map(ContentComparatorChange::getAttributeDataPath) 141 .collect(Collectors.toList()); 142 } 143 144// private Map<String, Object> _contentToJson(Content content) 145// { 146// return content.toJson(_changesView); 147// } 148 149 private String _saxedContent(String contentVersion) throws IOException 150 { 151 // FIXME REPOSITORY-454 use Content#toJson instead, to manipulate JSON and to be able to provide the view through a simple API call, instead of calling '/_content.xml' URI 152 // * when done, content#toJson will be available and the line for _targetContent can be uncommented, as well as declaration and calls of _contentToJson 153 // * when done, _changesView can be passed to this method 154 String uri; 155 try 156 { 157 // cocoon://_content.xml?contentId=... 158 uri = new URIBuilder() 159 .setScheme("cocoon") 160 .setHost("_content.xml") 161 .addParameter("contentId", _contentId) 162 .addParameter("contentVersion", contentVersion) 163 .addParameter("viewName", _viewName) 164 .addParameter("fallbackViewName", "main") 165 .addParameter("isEdition", "true") 166 .build() 167 .toString(); 168 } 169 catch (URISyntaxException e) 170 { 171 throw new IOException(e); 172 } 173 174 Source saxedContent = _sourceResolver.resolveURI(uri); 175 return IOUtils.toString(saxedContent.getInputStream(), StandardCharsets.UTF_8); 176 } 177 178 private View _retrieveEditionView() 179 { 180 String[] contentTypeIds = _targetContent.getTypes(); 181 String[] mixinIds = _targetContent.getMixinTypes(); 182 View view = _cTypesHelper.getView(_viewName, contentTypeIds, mixinIds); 183 if (view == null) 184 { 185 view = _cTypesHelper.getView("main", contentTypeIds, mixinIds); 186 } 187 return Objects.requireNonNull(view); 188 } 189 } 190}