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.content.external.ExternalizableMetadataProviderExtensionPoint; 039import org.ametys.cms.contenttype.ContentTypesHelper; 040import org.ametys.cms.repository.Content; 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.runtime.model.View; 046 047/** 048 * {@link ClientSideElement} for the tool for comparing a content between a base version and a target version. 049 */ 050public class CompareContentVersionToolClientSideElement extends StaticClientSideElement 051{ 052 private static final String __VIEW_NAME = "default-edition"; 053 private static final String __FALLBACK_VIEW_NAME = "main"; 054 055 private CompareVersionHelper _compareVersionHelper; 056 private SourceResolver _sourceResolver; 057 private ContentTypesHelper _cTypesHelper; 058 private ExternalizableMetadataProviderExtensionPoint _externalizableMetaProvider; 059 060 @Override 061 public void service(ServiceManager smanager) throws ServiceException 062 { 063 super.service(smanager); 064 _compareVersionHelper = (CompareVersionHelper) smanager.lookup(CompareVersionHelper.ROLE); 065 _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 066 _cTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 067 _externalizableMetaProvider = (ExternalizableMetadataProviderExtensionPoint) smanager.lookup(ExternalizableMetadataProviderExtensionPoint.ROLE); 068 } 069 070 /** 071 * Gets the JSON information about the changes for the given content id between the base and target provided versions. 072 * @param contentId The {@link Content} id 073 * @param targetVersion The content version to be compared 074 * @param baseVersion The base content version 075 * @return The information 076 * @throws IOException If an I/O exception occured during the comparison between the two versions, or when getting content values 077 * @throws ProcessingException If an exception occured when converting the "change" view 078 */ 079 @Callable 080 public Map<String, Object> getDiffValues(String contentId, String targetVersion, String baseVersion) throws IOException, ProcessingException 081 { 082 ContentVersionComparator comparator = new ContentVersionComparator(contentId, targetVersion, baseVersion); 083 comparator.compare(); 084 return comparator.resultToJson(); 085 } 086 087 private class ContentVersionComparator 088 { 089 private final String _contentId; 090 private final String _baseVersion; 091 private final String _targetVersion; 092 private Content _targetContent; 093 private ContentComparatorResult _comparatorResult; 094 private CompareView _resultCompareView; 095 096 ContentVersionComparator(String contentId, String version, String baseVersion) 097 { 098 _contentId = contentId; 099 _baseVersion = baseVersion; 100 _targetVersion = version; 101 } 102 103 void compare() throws IOException 104 { 105 _targetContent = _compareVersionHelper.getContentVersion(_contentId, _targetVersion); 106// _baseContent = _compareVersionHelper.getContentVersion(_contentId, _baseVersion); 107 108 _comparatorResult = _compareVersionHelper.compareVersions(_contentId, _targetVersion, _baseVersion); 109 110 View wrappedEditionView = _retrieveEditionView(); 111 Set<String> externalAndLocalMetadata = _externalizableMetaProvider.getExternalAndLocalMetadata(_targetContent); 112 _resultCompareView = new CompareView(wrappedEditionView, externalAndLocalMetadata); 113 } 114 115 Map<String, Object> resultToJson() throws IOException, ProcessingException 116 { 117 return Map.of( 118 "view", _resultCompareViewToJson(), 119 "changedAttributeDataPaths", _changedAttributeDataPaths(), 120 // FIXME REPOSITORY-454 Use "baseValues", _contentToJson(_baseContent), 121 "baseValues", _saxedContent(_baseVersion), 122 // FIXME REPOSITORY-454 Use "values", _contentToJson(_targetContent)); 123 "values", _saxedContent(_targetVersion)); 124 } 125 126 private Map<String, Object> _resultCompareViewToJson() throws ProcessingException 127 { 128 return _resultCompareView.toJSON(); 129 130 } 131 132 private Collection<String> _changedAttributeDataPaths() throws AmetysRepositoryException 133 { 134 List<ContentComparatorChange> contentComparatorChanges = _comparatorResult.getChanges(); 135 return _compareVersionHelper.filterChanges(contentComparatorChanges) 136 .map(ContentComparatorChange::getAttributeDataPath) 137 .collect(Collectors.toList()); 138 } 139 140// private Map<String, Object> _contentToJson(Content content) 141// { 142// return content.toJson(_changesView); 143// } 144 145 private String _saxedContent(String contentVersion) throws IOException 146 { 147 // 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 148 // * when done, content#toJson will be available and the line for _targetContent can be uncommented, as well as declaration and calls of _contentToJson 149 // * when done, _changesView can be passed to this method 150 String uri; 151 try 152 { 153 // cocoon://_content.xml?contentId=... 154 uri = new URIBuilder() 155 .setScheme("cocoon") 156 .setHost("_content.xml") 157 .addParameter("contentId", _contentId) 158 .addParameter("contentVersion", contentVersion) 159 .addParameter("metadataSetName", __VIEW_NAME) 160 .addParameter("fallbackMetadataSetName", __FALLBACK_VIEW_NAME) 161 .addParameter("isEditionMetadataSet", "true") 162 .build() 163 .toString(); 164 } 165 catch (URISyntaxException e) 166 { 167 throw new IOException(e); 168 } 169 170 Source saxedContent = _sourceResolver.resolveURI(uri); 171 return IOUtils.toString(saxedContent.getInputStream(), StandardCharsets.UTF_8); 172 } 173 174 private View _retrieveEditionView() 175 { 176 String[] contentTypeIds = _targetContent.getTypes(); 177 String[] mixinIds = _targetContent.getMixinTypes(); 178 View view = _cTypesHelper.getView(__VIEW_NAME, contentTypeIds, mixinIds); 179 if (view == null) 180 { 181 view = _cTypesHelper.getView(__FALLBACK_VIEW_NAME, contentTypeIds, mixinIds); 182 } 183 return Objects.requireNonNull(view); 184 } 185 } 186}