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(OrganisationChartPageHandler.ORGUNIT_CONTENT_TYPE);
091        ignoreContentTypes.add(UserDirectoryPageHandler.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}