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.CopyContentMetadataComponent;
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 * 
044 */
045public class CopyContentClientInteraction extends AbstractLogEnabled implements Component, Serviceable
046{
047    /** The ametys component that handle content copy */
048    protected CopyContentMetadataComponent _copyContentComponent;
049    
050    /** JSON helper */
051    protected JSONUtils _jsonUtils;
052    
053    @Override
054    public void service(ServiceManager sManager) throws ServiceException
055    {
056        _copyContentComponent = (CopyContentMetadataComponent) sManager.lookup(CopyContentMetadataComponent.ROLE);
057        _jsonUtils = (JSONUtils) sManager.lookup(JSONUtils.ROLE);
058    }
059    
060    /**
061     * Creates a content by copy of another one.<br>
062     * Also handle the possible inner duplication depending on the duplication mode for each metadata of type "content". 
063     * @param baseContentId The id of content to copy
064     * @param newContentTitle The title of content to create
065     * @param jsonMap The metadata to copy as a JSON string
066     * @param metadataSetNameToCopy The metadata set name to copy. Can be null
067     * @param metadataSetTypeToCopy The metadata set type 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    @Callable
074    public Map<String, Object> createContentByCopy(String baseContentId, String newContentTitle, String jsonMap, String metadataSetNameToCopy, String metadataSetTypeToCopy, int initActionId, int editActionId) throws Exception
075    {
076        return createContentByCopy(baseContentId, newContentTitle, null, jsonMap, metadataSetNameToCopy, metadataSetTypeToCopy, initActionId, editActionId);
077    }
078    
079    /**
080     * Creates a content by copy of another one.<br>
081     * Also handle the possible inner duplication depending on the duplication mode for each metadata of type "content". 
082     * @param baseContentId The id of content to copy
083     * @param newContentTitle The title of content to create
084     * @param jsonMap The metadata to copy as a JSON string
085     * @param metadataSetNameToCopy The metadata set name to copy. Can be null
086     * @param metadataSetTypeToCopy The metadata set type 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 metadataSetNameToCopy, String metadataSetTypeToCopy, 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 metadataSetName = StringUtils.defaultIfEmpty(metadataSetNameToCopy, "main");
104        String metadataSetType = StringUtils.defaultIfEmpty(metadataSetTypeToCopy, "edition");
105        
106        Map<String, Object> copyMap = jsonMap != null ? _jsonUtils.convertJsonToMap(jsonMap) : null;
107        
108        CopyReport copyReport = _copyContentComponent.copyContent(baseContentId, newContentTitle, copyMap, metadataSetName, metadataSetType, targetContentType, initActionId, editActionId);
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 metadata to copy as a JSON string
127     * @param metadataSetNameToCopy The metadata set name to copy. Can be null
128     * @param metadataSetTypeToCopy The metadata set type 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 metadataSetNameToCopy, String metadataSetTypeToCopy) 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 metadataSetName = StringUtils.defaultIfEmpty(metadataSetNameToCopy, "main");
143        String metadataSetType = StringUtils.defaultIfEmpty(metadataSetTypeToCopy, "edition");
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, metadataSetName, metadataSetType);
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>> metadataErrors = 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("metadataErrors", metadataErrors);
182        
183        for (CopyReport copyReport : copyReports)
184        {
185            _buildReport(copyReport, mainContent, createdContents, editedContents, failedContentCopies, metadataErrors, 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 metadataErrors The list of metadata 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>> metadataErrors)
201    {
202        _buildReport(copyReport, mainContent, createdContents, editedContents, failedContentCopies, metadataErrors, 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 metadataErrors The list of metadata 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>> metadataErrors, 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        // Metadata errors
255        for (I18nizableText metadataLabel : copyReport.getMetadataErrors())
256        {
257            Map<String, Object> errorEntry = new HashMap<>(4);
258            metadataErrors.add(errorEntry);
259            
260            errorEntry.put("label", metadataLabel);
261            errorEntry.put("contentId", copyReport.getTargetContentId());
262            errorEntry.put("contentTitle", copyReport.getTargetContentTitle());
263            errorEntry.put("contentIsReferenceTable", copyReport.getTargetContentIsReferenceTable());
264        }
265        
266        // Merge child reports.
267        for (CopyReport childReport : copyReport.getChildReports())
268        {
269            _buildReport(childReport, mainContent, createdContents, editedContents, failedContentCopies, metadataErrors);
270        }
271    }
272    
273    /**
274     * Retrieve the identifiers of the created contents through the copy report
275     * @param copyReport The report of the copy
276     * @return list of identifiers
277     */
278    protected List<String> _getCreatedContentIds(CopyReport copyReport)
279    {
280        List<String> contentIds = new ArrayList<>();
281        
282        if (CopyState.SUCCESS.equals(copyReport.getStatus()) && CopyMode.CREATION.equals(copyReport.getMode()))
283        {
284            contentIds.add(copyReport.getTargetContentId());
285        }
286        
287        for (CopyReport childReport : copyReport.getChildReports())
288        {
289            contentIds.addAll(_getCreatedContentIds(childReport));
290        }
291        
292        return contentIds;
293    }
294    
295    /**
296     * Retrieve the identifiers of the created contents through the copy reports
297     * @param copyReports list of copy reports
298     * @return list of identifiers
299     */
300    protected List<String> _getCreatedContentIds(List<CopyReport> copyReports)
301    {
302        List<String> contentIds = new ArrayList<>();
303        
304        for (CopyReport copyReport : copyReports)
305        {
306            contentIds.addAll(_getCreatedContentIds(copyReport));
307        }
308        
309        return contentIds;
310    }
311    
312    /**
313     * Retrieve the identifiers of the edited contents through the copy reports
314     * @param copyReports list of copy reports
315     * @return list of identifiers
316     */
317    protected List<String> _getEditedContentIds(List<CopyReport> copyReports)
318    {
319        List<String> contentIds = new ArrayList<>();
320        
321        // Edited content are only at the first level.
322        for (CopyReport copyReport : copyReports)
323        {
324            if (CopyState.SUCCESS.equals(copyReport.getStatus()) && CopyMode.EDITION.equals(copyReport.getMode()))
325            {
326                contentIds.add(copyReport.getTargetContentId());
327            }
328        }
329        
330        return contentIds;
331    }
332
333}