001/*
002 *  Copyright 2013 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.workflow.copy;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.commons.lang.StringUtils;
031import org.apache.commons.lang3.BooleanUtils;
032
033import org.ametys.cms.content.CopyContentComponent;
034import org.ametys.cms.content.CopyReport;
035import org.ametys.cms.content.CopyReport.CopyMode;
036import org.ametys.cms.content.CopyReport.CopyState;
037import org.ametys.core.ui.Callable;
038import org.ametys.core.util.JSONUtils;
039
040/**
041 * Component for content copy
042 */
043public class CopyContentClientInteraction extends AbstractLogEnabled implements Component, Serviceable
044{
045    /** Helper for actually edit and copy contents */
046    protected CopyContentComponent _copyContentComponent;
047    
048    private JSONUtils _jsonUtils;
049    
050    @Override
051    public void service(ServiceManager sManager) throws ServiceException
052    {
053        _copyContentComponent = (CopyContentComponent) sManager.lookup(CopyContentComponent.ROLE);
054        _jsonUtils = (JSONUtils) sManager.lookup(JSONUtils.ROLE);
055    }
056    
057    /**
058     * Creates a content by copy of another one.<br>
059     * Also handle the possible inner duplication depending on the duplication mode for each attribute of type "content". 
060     * @param baseContentId The id of content to copy
061     * @param newContentTitle The title of content to create
062     * @param jsonMap The attributes to copy as a JSON string
063     * @param viewNameToCopy The view name to copy. Can be null
064     * @param fallbackViewNameToCopy The fallback view name to use if 'viewNameToCopy' does not exist. Can be null
065     * @param viewModeToCopy The view mode to copy. Can be null
066     * @param initActionId The init workflow action id for copy
067     * @param editActionId The workflow action for editing content
068     * @return the copy result
069     * @throws Exception if an error occurred during copy
070     */
071    @Callable
072    public Map<String, Object> createContentByCopy(String baseContentId, String newContentTitle, String jsonMap, String viewNameToCopy, String fallbackViewNameToCopy, String viewModeToCopy, int initActionId, int editActionId) throws Exception
073    {
074        return createContentByCopy(baseContentId, newContentTitle, null, jsonMap, viewNameToCopy, fallbackViewNameToCopy, viewModeToCopy, initActionId, editActionId);
075    }
076    
077    /**
078     * Creates a content by copy of another one.<br>
079     * Also handle the possible inner duplication depending on the duplication mode for each attribute of type "content". 
080     * @param baseContentId The id of content to copy
081     * @param newContentTitle The title of content to create
082     * @param jsonMap The attributes to copy as a JSON string
083     * @param viewNameToCopy The view name to copy. Can be null
084     * @param fallbackViewNameToCopy The fallback view name to use if 'viewNameToCopy' does not exist. Can be null
085     * @param viewModeToCopy The view mode to copy. Can be null
086     * @param targetContentType The type of content to create. If null the type(s) of created content will be those of base content.
087     * @param initActionId The workflow action id for copy to init the new content
088     * @param editActionId The workflow action id for copy to edit the newly created content
089     * @return the copy result
090     * @throws Exception if an error occurred during copy
091     */
092    @Callable
093    public Map<String, Object> createContentByCopy(String baseContentId, String newContentTitle, String targetContentType, String jsonMap, String viewNameToCopy, String fallbackViewNameToCopy, String viewModeToCopy, int initActionId, int editActionId) throws Exception
094    {
095        Map<String, Object> result = new HashMap<> ();
096        
097        if (StringUtils.isEmpty(baseContentId))
098        {
099            throw new IllegalArgumentException("The id of content to copy parameter is mandatory.");
100        }
101        
102        String viewName = StringUtils.defaultIfEmpty(viewNameToCopy, "default-edition");
103        String fallbackViewName = StringUtils.defaultIfEmpty(fallbackViewNameToCopy, "main");
104        
105        Map<String, Object> copyMap = jsonMap != null ? _jsonUtils.convertJsonToMap(jsonMap) : null;
106        
107        CopyReport copyReport = _copyContentComponent.copyContent(baseContentId, newContentTitle, copyMap, viewName, fallbackViewName, targetContentType, initActionId);
108        
109        // CMS-5137 Open directly the main created content after copied
110        if (CopyState.SUCCESS.equals(copyReport.getStatus()))
111        {
112            result.put("mainContentId", copyReport.getTargetContentId());
113        }
114        
115        result.put("contentIds", _getCreatedContentIds(copyReport));
116        result.put("report", _buildReport(Arrays.asList(copyReport)));
117        
118        return result;
119    }
120    
121    /**
122     * Edits contents by copied of another one.<br>
123     * @param baseContentId The id of content to copy
124     * @param targetContentIds The ids of content to edit
125     * @param jsonMap The attributes to copy as a JSON string
126     * @param viewNameToCopy The view name to copy. Can be null.
127     * @param fallbackViewNameToCopy The fallback view name if 'viewName' is not found. Can be null.
128     * @param viewModeToCopy The view mode to copy. Can be null
129     * @return the copy result
130     * @throws Exception if an error occurred during copy
131     */
132    @Callable
133    public Map<String, Object> editContentByCopy(String baseContentId, List<String> targetContentIds, String jsonMap, String viewNameToCopy, String fallbackViewNameToCopy, String viewModeToCopy) throws Exception
134    {
135        Map<String, Object> result = new HashMap<> ();
136        
137        if (StringUtils.isEmpty(baseContentId))
138        {
139            throw new IllegalArgumentException("The id of content to copy parameter is mandatory.");
140        }
141        
142        String viewName = StringUtils.defaultIfEmpty(viewNameToCopy, "default-edition");
143        String fallbackViewName = StringUtils.defaultIfEmpty(fallbackViewNameToCopy, "main");
144        
145        Map<String, Object> copyMap = jsonMap != null ? _jsonUtils.convertJsonToMap(jsonMap) : null;
146        
147        List<CopyReport> copyReports = new LinkedList<>();
148        for (String targetContentId : targetContentIds)
149        {
150            CopyReport copyReport = _copyContentComponent.editContent(baseContentId, targetContentId, copyMap, viewName, fallbackViewName);
151            copyReports.add(copyReport);
152        }
153        
154        result.put("report", _buildReport(copyReports));
155        result.put("createdContentIds", _getCreatedContentIds(copyReports));
156        result.put("editedContentIds", _getEditedContentIds(copyReports));
157        
158        return result;
159    }
160    
161    
162    /**
163     * Build the copy report
164     * @param copyReports List of copy report
165     * @return The builded report 
166     */
167    protected Map<String, Object> _buildReport(List<CopyReport> copyReports)
168    {
169        Map<String, Object > reportMap = new HashMap<>();
170        
171        Map<String, Object> mainContent = new HashMap<>();
172        List<Map<String, Object>> createdContents = new LinkedList<>();
173        List<Map<String, Object>> editedContents = new LinkedList<>();
174        List<Map<String, Object>> failedContentCopies = new LinkedList<>();
175        List<Map<String, Object>> attributesErrors = new LinkedList<>();
176        
177        reportMap.put("mainContent", mainContent);
178        reportMap.put("createdContents", createdContents);
179        reportMap.put("editedContents", editedContents);
180        reportMap.put("failedContentCopies", failedContentCopies);
181        reportMap.put("attributesErrors", attributesErrors);
182        
183        for (CopyReport copyReport : copyReports)
184        {
185            _buildReport(copyReport, mainContent, createdContents, editedContents, failedContentCopies, attributesErrors, true);
186        }
187        
188        return reportMap;
189    }
190    
191    /**
192     * Helper method used to build the report (rootLevel is false)
193     * @param copyReport The report of the copy
194     * @param mainContent The map that will receive the copy report informations during a creation at rootLevel.
195     * @param createdContents The list that will receive the copy report informations during a creation at another level.
196     * @param editedContents The list that will receive the copy report informations during something else than a creation
197     * @param failedContentCopies The list that will receive th copy report informations if an error occurred during the copy
198     * @param attributesErrors The list of attributes errors reported
199     */
200    protected void _buildReport(CopyReport copyReport, Map<String, Object> mainContent, List<Map<String, Object>> createdContents, List<Map<String, Object>> editedContents, List<Map<String, Object>> failedContentCopies, List<Map<String, Object>> attributesErrors)
201    {
202        _buildReport(copyReport, mainContent, createdContents, editedContents, failedContentCopies, attributesErrors, false);
203    }
204
205    /**
206     * Helper method used to build the report
207     * @param copyReport The report of the copy
208     * @param mainContent The map that will receive the copy report informations during a creation at rootLevel.
209     * @param createdContents The list that will receive the copy report informations during a creation at another level.
210     * @param editedContents The list that will receive the copy report informations during something else than a creation
211     * @param failedContentCopies The list that will receive th copy report informations if an error occurred during the copy
212     * @param attributesErrors The list of attributes errors reported
213     * @param rootLevel True indicates the root level of the report.
214     */
215    protected void _buildReport(CopyReport copyReport, Map<String, Object> mainContent, List<Map<String, Object>> createdContents, List<Map<String, Object>> editedContents, List<Map<String, Object>> failedContentCopies, List<Map<String, Object>> attributesErrors, boolean rootLevel)
216    {
217        // success entry
218        if (CopyState.SUCCESS.equals(copyReport.getStatus()))
219        {
220            Map<String, Object> contentEntry = new HashMap<>(3);
221            contentEntry.put("id", copyReport.getTargetContentId());
222            contentEntry.put("title", copyReport.getTargetContentTitle());
223            contentEntry.put("isReferenceTable", copyReport.getTargetContentIsReferenceTable());
224            contentEntry.put("copiedAttachments", !copyReport.getCopiedAttachments().isEmpty());
225            
226            if (CopyMode.CREATION.equals(copyReport.getMode()))
227            {
228                if (rootLevel)
229                {
230                    // CMS-5137 do not include main created content in the report.
231                    mainContent.putAll(contentEntry);
232                }
233                else
234                {
235                    createdContents.add(contentEntry);
236                }
237            }
238            else
239            {
240                editedContents.add(contentEntry);
241            }
242        }
243        else
244        {
245            // error entry
246            Map<String, Object> failedCopyEntry = new HashMap<>(3);
247            failedContentCopies.add(failedCopyEntry);
248            
249            failedCopyEntry.put("id", copyReport.getBaseContentId());
250            failedCopyEntry.put("title", copyReport.getBaseContentTitle());
251            failedCopyEntry.put("isReferenceTable", BooleanUtils.isNotFalse(copyReport.getBaseContentIsReferenceTable()));
252        }
253        
254        // Merge child reports.
255        for (CopyReport childReport : copyReport.getChildReports())
256        {
257            _buildReport(childReport, mainContent, createdContents, editedContents, failedContentCopies, attributesErrors);
258        }
259    }
260    
261    /**
262     * Retrieve the identifiers of the created contents through the copy report
263     * @param copyReport The report of the copy
264     * @return list of identifiers
265     */
266    protected List<String> _getCreatedContentIds(CopyReport copyReport)
267    {
268        List<String> contentIds = new ArrayList<>();
269        
270        if (CopyState.SUCCESS.equals(copyReport.getStatus()) && CopyMode.CREATION.equals(copyReport.getMode()))
271        {
272            contentIds.add(copyReport.getTargetContentId());
273        }
274        
275        for (CopyReport childReport : copyReport.getChildReports())
276        {
277            contentIds.addAll(_getCreatedContentIds(childReport));
278        }
279        
280        return contentIds;
281    }
282    
283    /**
284     * Retrieve the identifiers of the created contents through the copy reports
285     * @param copyReports list of copy reports
286     * @return list of identifiers
287     */
288    protected List<String> _getCreatedContentIds(List<CopyReport> copyReports)
289    {
290        List<String> contentIds = new ArrayList<>();
291        
292        for (CopyReport copyReport : copyReports)
293        {
294            contentIds.addAll(_getCreatedContentIds(copyReport));
295        }
296        
297        return contentIds;
298    }
299    
300    /**
301     * Retrieve the identifiers of the edited contents through the copy reports
302     * @param copyReports list of copy reports
303     * @return list of identifiers
304     */
305    protected List<String> _getEditedContentIds(List<CopyReport> copyReports)
306    {
307        List<String> contentIds = new ArrayList<>();
308        
309        // Edited content are only at the first level.
310        for (CopyReport copyReport : copyReports)
311        {
312            if (CopyState.SUCCESS.equals(copyReport.getStatus()) && CopyMode.EDITION.equals(copyReport.getMode()))
313            {
314                contentIds.add(copyReport.getTargetContentId());
315            }
316        }
317        
318        return contentIds;
319    }
320}