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 fallbackMetadataSetNameToCopy The fallback metadata set name to use if 'metadataSetNameToCopy' does not exist. Can be null
068     * @param metadataSetTypeToCopy The metadata set type to copy. Can be null
069     * @param initActionId The init workflow action id for copy
070     * @param editActionId The workflow action for editing content
071     * @return the copy result
072     * @throws Exception if an error occurred during copy
073     */
074    @Callable
075    public Map<String, Object> createContentByCopy(String baseContentId, String newContentTitle, String jsonMap, String metadataSetNameToCopy, String fallbackMetadataSetNameToCopy, String metadataSetTypeToCopy, int initActionId, int editActionId) throws Exception
076    {
077        return createContentByCopy(baseContentId, newContentTitle, null, jsonMap, metadataSetNameToCopy, fallbackMetadataSetNameToCopy, metadataSetTypeToCopy, 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 metadata 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 metadata to copy as a JSON string
086     * @param metadataSetNameToCopy The metadata set name to copy. Can be null
087     * @param fallbackMetadataSetNameToCopy The fallback metadata set name to use if 'metadataSetNameToCopy' does not exist. Can be null
088     * @param metadataSetTypeToCopy The metadata set type 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    @Callable
096    public Map<String, Object> createContentByCopy(String baseContentId, String newContentTitle, String targetContentType, String jsonMap, String metadataSetNameToCopy, String fallbackMetadataSetNameToCopy, String metadataSetTypeToCopy, int initActionId, int editActionId) throws Exception
097    {
098        Map<String, Object> result = new HashMap<> ();
099        
100        if (StringUtils.isEmpty(baseContentId))
101        {
102            throw new IllegalArgumentException("The id of content to copy parameter is mandatory.");
103        }
104        
105        String metadataSetName = StringUtils.defaultIfEmpty(metadataSetNameToCopy, "default-edition");
106        String fallbackMetadataSetName = StringUtils.defaultIfEmpty(fallbackMetadataSetNameToCopy, "main");
107        String metadataSetType = StringUtils.defaultIfEmpty(metadataSetTypeToCopy, "edition");
108        
109        Map<String, Object> copyMap = jsonMap != null ? _jsonUtils.convertJsonToMap(jsonMap) : null;
110        
111        CopyReport copyReport = _copyContentComponent.copyContent(baseContentId, newContentTitle, copyMap, metadataSetName, fallbackMetadataSetName, metadataSetType, targetContentType, initActionId, editActionId);
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 metadata to copy as a JSON string
130     * @param metadataSetNameToCopy The metadata set name to copy. Can be null.
131     * @param fallbackMetadataSetNameToCopy The fallback metadata set name if 'metadataSetName' is not found. Can be null.
132     * @param metadataSetTypeToCopy The metadata set type to copy. Can be null
133     * @return the copy result
134     * @throws Exception if an error occurred during copy
135     */
136    @Callable
137    public Map<String, Object> editContentByCopy(String baseContentId, List<String> targetContentIds, String jsonMap, String metadataSetNameToCopy, String fallbackMetadataSetNameToCopy, String metadataSetTypeToCopy) throws Exception
138    {
139        Map<String, Object> result = new HashMap<> ();
140        
141        if (StringUtils.isEmpty(baseContentId))
142        {
143            throw new IllegalArgumentException("The id of content to copy parameter is mandatory.");
144        }
145        
146        String metadataSetName = StringUtils.defaultIfEmpty(metadataSetNameToCopy, "default-edition");
147        String fallbackMetadataSetName = StringUtils.defaultIfEmpty(fallbackMetadataSetNameToCopy, "main");
148        String metadataSetType = StringUtils.defaultIfEmpty(metadataSetTypeToCopy, "edition");
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, metadataSetName, fallbackMetadataSetName, metadataSetType);
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>> metadataErrors = 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("metadataErrors", metadataErrors);
187        
188        for (CopyReport copyReport : copyReports)
189        {
190            _buildReport(copyReport, mainContent, createdContents, editedContents, failedContentCopies, metadataErrors, 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 metadataErrors The list of metadata 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>> metadataErrors)
206    {
207        _buildReport(copyReport, mainContent, createdContents, editedContents, failedContentCopies, metadataErrors, 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 metadataErrors The list of metadata 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>> metadataErrors, 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        }
258        
259        // Metadata errors
260        for (I18nizableText metadataLabel : copyReport.getMetadataErrors())
261        {
262            Map<String, Object> errorEntry = new HashMap<>(4);
263            metadataErrors.add(errorEntry);
264            
265            errorEntry.put("label", metadataLabel);
266            errorEntry.put("contentId", copyReport.getTargetContentId());
267            errorEntry.put("contentTitle", copyReport.getTargetContentTitle());
268            errorEntry.put("contentIsReferenceTable", copyReport.getTargetContentIsReferenceTable());
269        }
270        
271        // Merge child reports.
272        for (CopyReport childReport : copyReport.getChildReports())
273        {
274            _buildReport(childReport, mainContent, createdContents, editedContents, failedContentCopies, metadataErrors);
275        }
276    }
277    
278    /**
279     * Retrieve the identifiers of the created contents through the copy report
280     * @param copyReport The report of the copy
281     * @return list of identifiers
282     */
283    protected List<String> _getCreatedContentIds(CopyReport copyReport)
284    {
285        List<String> contentIds = new ArrayList<>();
286        
287        if (CopyState.SUCCESS.equals(copyReport.getStatus()) && CopyMode.CREATION.equals(copyReport.getMode()))
288        {
289            contentIds.add(copyReport.getTargetContentId());
290        }
291        
292        for (CopyReport childReport : copyReport.getChildReports())
293        {
294            contentIds.addAll(_getCreatedContentIds(childReport));
295        }
296        
297        return contentIds;
298    }
299    
300    /**
301     * Retrieve the identifiers of the created contents through the copy reports
302     * @param copyReports list of copy reports
303     * @return list of identifiers
304     */
305    protected List<String> _getCreatedContentIds(List<CopyReport> copyReports)
306    {
307        List<String> contentIds = new ArrayList<>();
308        
309        for (CopyReport copyReport : copyReports)
310        {
311            contentIds.addAll(_getCreatedContentIds(copyReport));
312        }
313        
314        return contentIds;
315    }
316    
317    /**
318     * Retrieve the identifiers of the edited contents through the copy reports
319     * @param copyReports list of copy reports
320     * @return list of identifiers
321     */
322    protected List<String> _getEditedContentIds(List<CopyReport> copyReports)
323    {
324        List<String> contentIds = new ArrayList<>();
325        
326        // Edited content are only at the first level.
327        for (CopyReport copyReport : copyReports)
328        {
329            if (CopyState.SUCCESS.equals(copyReport.getStatus()) && CopyMode.EDITION.equals(copyReport.getMode()))
330            {
331                contentIds.add(copyReport.getTargetContentId());
332            }
333        }
334        
335        return contentIds;
336    }
337
338}