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