001/* 002 * Copyright 2017 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.odf.workflow.copy; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.stream.Collectors; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.commons.lang.StringUtils; 029 030import org.ametys.cms.ObservationConstants; 031import org.ametys.cms.clientsideelement.SmartContentClientSideElement; 032import org.ametys.cms.indexing.solr.SolrIndexHelper; 033import org.ametys.cms.repository.Content; 034import org.ametys.core.ui.Callable; 035import org.ametys.core.util.JSONUtils; 036import org.ametys.odf.CopyODFUpdater; 037import org.ametys.odf.ODFHelper; 038import org.ametys.odf.ProgramItem; 039import org.ametys.odf.content.CopyODFContentUpdaterExtensionPoint; 040import org.ametys.odf.course.Course; 041import org.ametys.runtime.i18n.I18nizableText; 042 043/** 044 * Client side element for ODF content copy 045 * 046 */ 047public class CopyODFContentClientSideElement extends SmartContentClientSideElement 048{ 049 /** The key to get the duplication mode */ 050 public static final String DUPLICATION_MODE_KEY = "$duplicationMode"; 051 052 /** The key to get the parent ProgramPart's id */ 053 public static final String PARENT_KEY = "$parent"; 054 055 /** The key to tell if we keep the creation title */ 056 public static final String KEEP_CREATION_TITLE_KEY = "$keepCreationTitle"; 057 058 /** The ODF helper */ 059 protected ODFHelper _odfHelper; 060 /** The client side element for copy */ 061 protected CopyContentClientInteraction _copyClientSideInteraction; 062 /** JSON utils */ 063 protected JSONUtils _jsonUtils; 064 /** The Solr index helper */ 065 protected SolrIndexHelper _solrIndexHelper; 066 /** The copy ODF content updater extension point */ 067 protected CopyODFContentUpdaterExtensionPoint _copyODFContentUpdaterEP; 068 069 @Override 070 public void service(ServiceManager sManager) throws ServiceException 071 { 072 super.service(sManager); 073 _odfHelper = (ODFHelper) sManager.lookup(ODFHelper.ROLE); 074 _copyClientSideInteraction = (CopyContentClientInteraction) sManager.lookup(CopyContentClientInteraction.class.getName()); 075 _jsonUtils = (JSONUtils) sManager.lookup(JSONUtils.ROLE); 076 _solrIndexHelper = (SolrIndexHelper) sManager.lookup(SolrIndexHelper.ROLE); 077 _copyODFContentUpdaterEP = (CopyODFContentUpdaterExtensionPoint) sManager.lookup(CopyODFContentUpdaterExtensionPoint.ROLE); 078 } 079 080 /** 081 * Determines if a ODF content can be copied and linked to a target content 082 * @param copiedContentId The id of copied content 083 * @param targetContentId The id of target content 084 * @param contextualParameters the contextual parameters 085 * @return the result with success to true if copy is available 086 */ 087 @Callable 088 public Map<String, Object> canCopyTo(String copiedContentId, String targetContentId, Map<String, Object> contextualParameters) 089 { 090 Content copiedContent = _resolver.resolveById(copiedContentId); 091 Content targetContent = _resolver.resolveById(targetContentId); 092 093 Map<String, Object> result = new HashMap<>(); 094 095 // Put the mode to copy to prevent to check shareable course fields 096 contextualParameters.put("mode", "copy"); 097 098 List<I18nizableText> errors = new ArrayList<>(); 099 if (!canCopyTo(copiedContent, targetContent, errors, contextualParameters)) 100 { 101 // Invalid target 102 result.put("errorMessages", errors); 103 result.put("success", false); 104 } 105 else 106 { 107 result.put("success", true); 108 } 109 110 return result; 111 } 112 113 /** 114 * Determines if a ODF content can be copied and linked to a target content 115 * @param copiedContent The copied content 116 * @param targetContent The target content 117 * @param errors The list of error messages 118 * @param contextualParameters the contextual parameters 119 * @return true if the relation is valid, false otherwise 120 */ 121 protected boolean canCopyTo(Content copiedContent, Content targetContent, List<I18nizableText> errors, Map<String, Object> contextualParameters) 122 { 123 return _odfHelper.isRelationCompatible(copiedContent, targetContent, errors, contextualParameters); 124 } 125 126 /** 127 * Creates a content by copy of another one.<br> 128 * Also handle the possible inner duplication depending on the duplication mode for each attribute of type "content". 129 * @param baseContentId The id of content to copy 130 * @param newContentTitle The title of content to create 131 * @param viewNameToCopy The view name to copy. Can be null 132 * @param fallbackViewNameToCopy The fallback view name to use if 'viewNameToCopy' does not exist. Can be null 133 * @param viewMode The view type to copy. Can be null 134 * @param initActionId The init workflow action id for copy 135 * @param editActionId The workflow action for editing content 136 * @param duplicationModeAsString the duplication mode 137 * @param parentContentId the parent id under which the duplicated content will be created. Can be null 138 * @return the copy result 139 * @throws Exception if an error occurred during copy 140 */ 141 @Callable 142 public Map<String, Object> createContentByCopy(String baseContentId, String newContentTitle, String viewNameToCopy, String fallbackViewNameToCopy, String viewMode, int initActionId, int editActionId, String duplicationModeAsString, String parentContentId) throws Exception 143 { 144 Map<String, Object> result = new HashMap<>(); 145 result.put("locked-contents", new ArrayList<>()); 146 147 String [] handledEventIds = new String[] {ObservationConstants.EVENT_CONTENT_ADDED, ObservationConstants.EVENT_CONTENT_MODIFIED, ObservationConstants.EVENT_CONTENT_WORKFLOW_CHANGED}; 148 try 149 { 150 _solrIndexHelper.pauseSolrCommitForEvents(handledEventIds); 151 152 DuplicationMode duplicationMode = StringUtils.isNotBlank(duplicationModeAsString) ? DuplicationMode.valueOf(duplicationModeAsString.toUpperCase()) : DuplicationMode.SINGLE; 153 154 if (!checkBeforeDuplication(baseContentId, parentContentId, duplicationMode, result)) 155 { 156 result.put("check-before-duplication-failed", true); 157 } 158 else 159 { 160 Map<String, Object> copyMap = new HashMap<>(); 161 copyMap.put(DUPLICATION_MODE_KEY, duplicationMode.toString()); 162 copyMap.put(PARENT_KEY, parentContentId); 163 164 String jsonMap = _jsonUtils.convertObjectToJson(copyMap); 165 result = _copyClientSideInteraction.createContentByCopy(baseContentId, newContentTitle, jsonMap, viewNameToCopy, fallbackViewNameToCopy, viewMode, initActionId, editActionId); 166 167 if (result.containsKey("contentIds")) 168 { 169 @SuppressWarnings("unchecked") 170 Map<String, String> contentIds = (Map<String, String>) result.getOrDefault("contentIds", new HashMap<>()); 171 Map<Content, Content> createdContents = contentIds.entrySet() 172 .stream() 173 .collect(Collectors.toMap( 174 e -> (Content) _resolver.resolveById(e.getKey()), 175 e -> (Content) _resolver.resolveById(e.getValue()) 176 ) 177 ); 178 179 ProgramItem baseContent = _resolver.resolveById(baseContentId); 180 String catalog = baseContent.getCatalog(); 181 Content parentContent = StringUtils.isNotBlank(parentContentId) ? _resolver.resolveById(parentContentId) : null; 182 for (String id : _copyODFContentUpdaterEP.getExtensionsIds()) 183 { 184 CopyODFUpdater updater = _copyODFContentUpdaterEP.getExtension(id); 185 updater.updateContents(catalog, catalog, createdContents, parentContent); 186 } 187 } 188 } 189 } 190 finally 191 { 192 _solrIndexHelper.restartSolrCommitForEvents(handledEventIds); 193 } 194 195 return result; 196 } 197 198 /** 199 * Check that duplication can be performed without blocking errors 200 * @param contentId The content id to copy 201 * @param parentContentId The parent content id 202 * @param duplicationMode The duplication mode 203 * @param results the results map 204 * @return true if the duplication can be performed 205 */ 206 protected boolean checkBeforeDuplication(String contentId, String parentContentId, DuplicationMode duplicationMode, Map<String, Object> results) 207 { 208 boolean allRight = true; 209 if (StringUtils.isNotBlank(parentContentId)) 210 { 211 // Check if the parent is locked 212 Content parentContent = _resolver.resolveById(parentContentId); 213 if (_isLocked(parentContent)) 214 { 215 @SuppressWarnings("unchecked") 216 List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents"); 217 Map<String, Object> contentParams = getContentDefaultParameters (parentContent); 218 contentParams.put("description", _getLockedDescription(parentContent)); 219 lockedContents.add(contentParams); 220 } 221 } 222 223 Content content = _resolver.resolveById(contentId); 224 if (content instanceof ProgramItem programItem) 225 { 226 if (duplicationMode == DuplicationMode.SINGLE) 227 { 228 // Check if the child are locked 229 for (ProgramItem programItemChild : _odfHelper.getChildProgramItems(programItem)) 230 { 231 if (_isLocked((Content) programItemChild)) 232 { 233 @SuppressWarnings("unchecked") 234 List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents"); 235 Map<String, Object> contentParams = getContentDefaultParameters ((Content) programItemChild); 236 contentParams.put("description", _getLockedDescription((Content) programItemChild)); 237 lockedContents.add(contentParams); 238 239 allRight = false; 240 } 241 } 242 } 243 else if (duplicationMode == DuplicationMode.STRUCTURE_ONLY) 244 { 245 // Check if course child are locked 246 for (Course course : _getCourse(programItem)) 247 { 248 if (_isLocked(course)) 249 { 250 @SuppressWarnings("unchecked") 251 List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents"); 252 Map<String, Object> contentParams = getContentDefaultParameters (course); 253 contentParams.put("description", _getLockedDescription(course)); 254 lockedContents.add(contentParams); 255 256 allRight = false; 257 } 258 } 259 } 260 } 261 262 return allRight; 263 } 264 265 /** 266 * Get all first courses in sub item of the program item 267 * @param programItem the program item 268 * @return a set of courses 269 */ 270 protected Set<Course> _getCourse(ProgramItem programItem) 271 { 272 Set<Course> courses = new HashSet<>(); 273 for (ProgramItem programItemChild : _odfHelper.getChildProgramItems(programItem)) 274 { 275 if (programItemChild instanceof Course) 276 { 277 courses.add((Course) programItemChild); 278 } 279 else 280 { 281 courses.addAll(_getCourse(programItemChild)); 282 } 283 } 284 285 return courses; 286 } 287 288 /** 289 * Enumeration for the mode of duplication 290 */ 291 public enum DuplicationMode 292 { 293 /** Duplicate the content only */ 294 SINGLE, 295 /** Duplicate the content and its structure */ 296 STRUCTURE_ONLY, 297 /** Duplicate the content and its structure and its courses */ 298 FULL 299 } 300}