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