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("viewName", __VIEW_NAME)
160                        .addParameter("fallbackViewName", __FALLBACK_VIEW_NAME)
161                        .addParameter("isEdition", "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}