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