001/* 002 * Copyright 2018 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.plugins.userdirectory; 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; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.commons.lang3.StringUtils; 028import org.apache.commons.lang3.tuple.Pair; 029import org.slf4j.Logger; 030 031import org.ametys.cms.content.ContentHelper; 032import org.ametys.cms.repository.Content; 033import org.ametys.cms.repository.WorkflowAwareContent; 034import org.ametys.cms.workflow.ContentWorkflowHelper; 035import org.ametys.cms.workflow.EditContentFunction; 036import org.ametys.core.util.I18nUtils; 037import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus; 038import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionDataProvider; 039import org.ametys.plugins.repository.data.external.ExternalizableDataProviderExtensionPoint; 040import org.ametys.plugins.repository.data.holder.values.SynchronizableValue; 041import org.ametys.plugins.repository.data.holder.values.SynchronizableValue.Mode; 042import org.ametys.plugins.workflow.AbstractWorkflowComponent; 043import org.ametys.runtime.i18n.I18nizableText; 044import org.ametys.runtime.model.ModelItem; 045 046import com.opensymphony.workflow.InvalidActionException; 047import com.opensymphony.workflow.WorkflowException; 048 049/** 050 * Delete orgunit component 051 */ 052public class DeleteOrgUnitComponent extends AbstractDeleteUDContentComponent 053{ 054 /** The avalon role. */ 055 public static final String ROLE = DeleteOrgUnitComponent.class.getName(); 056 057 /** Constant for storing the scc identifier into the parameters map */ 058 public static final String SCC_ID_PARAMETERS_KEY = "sccId"; 059 060 /** The organisation chart page handler */ 061 protected OrganisationChartPageHandler _oCPageHandler; 062 063 /** The content helper */ 064 protected ContentHelper _contentHelper; 065 066 /** The i18n utils */ 067 protected I18nUtils _i18nUtils; 068 069 /** The content workflow helper */ 070 protected ContentWorkflowHelper _contentWorkflowHelper; 071 072 /** The external data provider extension point */ 073 protected ExternalizableDataProviderExtensionPoint _externalizableDataProviderExtensionPoint; 074 075 @Override 076 public void service(ServiceManager smanager) throws ServiceException 077 { 078 super.service(smanager); 079 _oCPageHandler = (OrganisationChartPageHandler) smanager.lookup(OrganisationChartPageHandler.ROLE); 080 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 081 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 082 _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); 083 _externalizableDataProviderExtensionPoint = (ExternalizableDataProviderExtensionPoint) smanager.lookup(ExternalizableDataProviderExtensionPoint.ROLE); 084 } 085 086 @Override 087 public boolean isContentReferenced(Content content, Logger logger) 088 { 089 List<String> ignoreContentTypes = new ArrayList<>(); 090 ignoreContentTypes.add(UserDirectoryHelper.ORGUNIT_CONTENT_TYPE); 091 ignoreContentTypes.add(UserDirectoryHelper.ABSTRACT_USER_CONTENT_TYPE); 092 093 return _contentHelper.hasReferencingContents(content, ignoreContentTypes, true); 094 } 095 096 @Override 097 protected boolean _checkBeforeDeletion(Content content, Map<String, String> rights, Map<String, Object> results, Logger logger) 098 { 099 // Check right and lock on content it self 100 boolean allRight = _canDeleteContent(content, rights, results); 101 102 // Check lock on parent contents 103 allRight = _checkParentBeforeDeletion(content, results) && allRight; 104 105 // Check lock on users contents 106 allRight = _checkUsersBeforeDeletion(content, results) && allRight; 107 108 // Check right and lock on children to be deleted or modified 109 allRight = _checkChildrenBeforeDeletion(content, rights, results, logger) && allRight; 110 111 return allRight; 112 } 113 114 /** 115 * True if the parent content is not locked 116 * @param content the content 117 * @param results the results map 118 * @return true if the parent content is not locked 119 */ 120 protected boolean _checkParentBeforeDeletion(Content content, Map<String, Object> results) 121 { 122 boolean allRight = true; 123 124 // Check if parents are not locked 125 Content parentContent = _oCPageHandler.getParentContent(content); 126 if (_isLocked(parentContent)) 127 { 128 @SuppressWarnings("unchecked") 129 List<Content> lockedContents = (List<Content>) results.get("locked-contents"); 130 lockedContents.add(parentContent); 131 132 allRight = false; 133 } 134 135 return allRight; 136 } 137 138 /** 139 * True if the users content is not locked 140 * @param content the content 141 * @param results the results map 142 * @return true if the parent content is not locked 143 */ 144 protected boolean _checkUsersBeforeDeletion(Content content, Map<String, Object> results) 145 { 146 boolean allRight = true; 147 148 // Check if users are not locked 149 for (Content user : _oCPageHandler.getUserContents(content)) 150 { 151 if (_isLocked(user)) 152 { 153 @SuppressWarnings("unchecked") 154 List<Content> lockedContents = (List<Content>) results.get("locked-contents"); 155 lockedContents.add(user); 156 157 allRight = false; 158 } 159 } 160 161 return allRight; 162 } 163 164 /** 165 * Browse children to check if deletion could succeed 166 * @param contentToCheck The current content to check 167 * @param results The result 168 * @param rights the map of rights id with its prefix 169 * @param logger The logger 170 * @return true if the deletion can be processed 171 */ 172 protected boolean _checkChildrenBeforeDeletion(Content contentToCheck, Map<String, String> rights, Map<String, Object> results, Logger logger) 173 { 174 boolean allRight = true; 175 176 List<Content> childOrgUnits = _oCPageHandler.getChildContents(contentToCheck); 177 for (Content childOrgUnit : childOrgUnits) 178 { 179 if (!_canDeleteContent(childOrgUnit, rights, results)) 180 { 181 allRight = false; 182 } 183 else if (isContentReferenced(childOrgUnit, logger)) 184 { 185 @SuppressWarnings("unchecked") 186 List<Content> referencedContents = (List<Content>) results.get("referenced-contents"); 187 referencedContents.add(childOrgUnit); 188 189 allRight = false; 190 } 191 else 192 { 193 // Browse children recursively 194 allRight = _checkChildrenBeforeDeletion(childOrgUnit, rights, results, logger) && allRight; 195 } 196 } 197 198 return allRight; 199 } 200 201 @Override 202 protected boolean _removeRelations(Content orgUnit, Map<String, Object> parameters, Logger logger) 203 { 204 boolean success = true; 205 206 // Delete relation to parent and users 207 List<Pair<String, Content>> incomingReferences = _contentHelper.getReferencingContents(orgUnit); 208 for (Pair<String, Content> reference : incomingReferences) 209 { 210 Content refContent = reference.getRight(); 211 String valuePath = reference.getLeft(); 212 213 try 214 { 215 // Ignore child orgunits because they are going to be deleted 216 if (valuePath.equals(OrganisationChartPageHandler.CHILD_ORGUNIT_ATTRIBUTE_NAME)) 217 { 218 I18nizableText commentText = new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_WORKFLOW_ACTION_REMOVE_ORGUNIT_REFERENCE_MSG"); 219 String comment = _i18nUtils.translate(commentText, refContent.getLanguage()); 220 221 _removeRelation((WorkflowAwareContent) refContent, parameters, valuePath, orgUnit, comment); 222 } 223 else if (valuePath.equals(UserDirectoryHelper.ORGUNITS_ATTRIBUTE)) 224 { 225 I18nizableText commentText = new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_WORKFLOW_ACTION_REMOVE_USER_REFERENCE_MSG"); 226 String comment = _i18nUtils.translate(commentText, refContent.getLanguage()); 227 228 _removeRelation((WorkflowAwareContent) refContent, parameters, valuePath, orgUnit, comment); 229 } 230 } 231 catch (WorkflowException | InvalidActionException e) 232 { 233 logger.error("Unable to remove relation to content \"{}\" ({}) for referencing content \"{}\" ({}) ", orgUnit.getTitle(), orgUnit.getId(), refContent.getTitle(), refContent.getId(), e); 234 success = false; 235 } 236 } 237 238 return success; 239 } 240 241 private void _removeRelation(WorkflowAwareContent content, Map<String, Object> parameters, String attribute, Content orgUnit, String comment) throws WorkflowException 242 { 243 ModelItem modelItem = content.getDefinition(attribute); 244 Map<String, Object> externalizableDataContext = new HashMap<>(); 245 if (parameters.containsKey(SCC_ID_PARAMETERS_KEY)) 246 { 247 externalizableDataContext.put(SynchronizableContentsCollectionDataProvider.SCC_ID_CONTEXT_KEY, parameters.get(SCC_ID_PARAMETERS_KEY)); 248 } 249 250 ExternalizableDataStatus status = _externalizableDataProviderExtensionPoint.isDataExternalizable(content, modelItem, externalizableDataContext) ? ExternalizableDataStatus.EXTERNAL : ExternalizableDataStatus.LOCAL; 251 SynchronizableValue value = new SynchronizableValue(List.of(orgUnit.getId()), status); 252 value.setMode(Mode.REMOVE); 253 254 Map<String, Object> values = Map.of(attribute, value); 255 256 Map<String, Object> inputs = new HashMap<>(); 257 if (StringUtils.isNotEmpty(comment)) 258 { 259 inputs.put("comment", comment); 260 } 261 262 Map<String, Object> actionParameters = new HashMap<>(); 263 264 actionParameters.put(EditContentFunction.VALUES_KEY, values); 265 actionParameters.put(EditContentFunction.QUIT, true); 266 inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, actionParameters); 267 268 _contentWorkflowHelper.doAction(content, _removeReferenceActionId, inputs); 269 } 270 271 272 @Override 273 protected Set<String> _getContentIdsToDelete(Content content, Map<String, Object> parameters, Map<String, String> rights, Map<String, Object> results, Logger logger) 274 { 275 Set<String> toDelete = new HashSet<>(); 276 277 if (_canDeleteContent(content, rights, results)) 278 { 279 toDelete.add(content.getId()); 280 281 for (Content childOrgUnit : _oCPageHandler.getChildContents(content)) 282 { 283 if (!isContentReferenced(childOrgUnit, logger) && _removeUsersRelation(childOrgUnit, parameters, logger)) 284 { 285 toDelete.addAll(_getContentIdsToDelete(childOrgUnit, parameters, rights, results, logger)); 286 } 287 else 288 { 289 // The child program item can not be deleted, remove the relation to the parent and stop iteration 290 @SuppressWarnings("unchecked") 291 List<Content> referencedContents = (List<Content>) results.get("referenced-contents"); 292 referencedContents.add(childOrgUnit); 293 } 294 } 295 } 296 297 return toDelete; 298 } 299 300 /** 301 * Remove users relation 302 * @param orgUnit the orgunit content 303 * @param parameters the additional parameters 304 * @param logger The logger 305 * @return true if relations have been removed 306 */ 307 protected boolean _removeUsersRelation(Content orgUnit, Map<String, Object> parameters, Logger logger) 308 { 309 boolean success = true; 310 311 // Delete relation users 312 List<Pair<String, Content>> incomingReferences = _contentHelper.getReferencingContents(orgUnit); 313 for (Pair<String, Content> refPair : incomingReferences) 314 { 315 Content refContent = refPair.getValue(); 316 String valuePath = refPair.getKey(); 317 318 try 319 { 320 // Just remove when path is from users 321 if (valuePath.equals(UserDirectoryHelper.ORGUNITS_ATTRIBUTE)) 322 { 323 I18nizableText commentText = new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_WORKFLOW_ACTION_REMOVE_REFERENCE_MSG"); 324 String comment = _i18nUtils.translate(commentText, refContent.getLanguage()); 325 326 _removeRelation((WorkflowAwareContent) refContent, parameters, valuePath, orgUnit, comment); 327 } 328 } 329 catch (WorkflowException | InvalidActionException e) 330 { 331 logger.error("Unable to remove relations to content \"" + orgUnit.getTitle() + " (" + orgUnit.getId() + ")", e); 332 success = false; 333 } 334 } 335 336 return success; 337 } 338}