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.cms.content.referencetable;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.commons.collections.CollectionUtils;
030
031import org.ametys.cms.clientsideelement.relations.SetContentAttributeClientSideElement;
032import org.ametys.cms.contenttype.ContentAttributeDefinition;
033import org.ametys.cms.contenttype.ContentType;
034import org.ametys.cms.repository.Content;
035import org.ametys.core.ui.Callable;
036import org.ametys.plugins.repository.AmetysRepositoryException;
037import org.ametys.runtime.parameter.Validator;
038
039/**
040 * Set the "parent" attribute of a simple content
041 */
042public class SetParentContentClientSideElement extends SetContentAttributeClientSideElement
043{
044    /** The helper component for hierarchical simple contents */
045    protected HierarchicalReferenceTablesHelper _hierarchicalSimpleContentsHelper;
046    
047    @Override
048    public void service(ServiceManager manager) throws ServiceException
049    {
050        super.service(manager);
051        _hierarchicalSimpleContentsHelper = (HierarchicalReferenceTablesHelper) manager.lookup(HierarchicalReferenceTablesHelper.ROLE);
052    }
053    
054    /**
055     * Tells if the given simple contents can have their parent attribute changed for the given new simple content parent.
056     * @param contentIdsToMove The identifiers of the simple contents willing to change their parent attribute
057     * @param newParentContentId The identifier of the new parent simple content. Can be "root" if it is the root of simple contents
058     * @param leafContentTypeId If newParentContentId is equal to "root", is the identifier of the content type of the leaves of the tree. Otherwise, must be null
059     * @return A map with key 'validContents' and key 'invalidContents'
060     */
061    @Callable
062    public Map<String, Object> areValidMoves(List<String> contentIdsToMove, String newParentContentId, String leafContentTypeId)
063    {
064        Map<String, Object> result = new HashMap<>();
065        List<Map<String, String>> validContents = new ArrayList<>();
066        result.put("validContents", validContents);
067        List<String> invalidContents = new ArrayList<>(); 
068        result.put("invalidContents", invalidContents);
069        
070        if ("root".equals(newParentContentId))
071        {
072            ContentType leafContentType = _contentTypeExtensionPoint.getExtension(leafContentTypeId);
073            String topLevelType = _hierarchicalSimpleContentsHelper.getTopLevelType(leafContentType).getId();
074            
075            for (String contentIdToMove : contentIdsToMove)
076            {
077                try
078                {
079                    Content contentToMove = _resolver.resolveById(contentIdToMove);
080                    ContentAttributeDefinition parentAttributeDefinition = _getParentAttributeDefinition(contentToMove);
081                    if (parentAttributeDefinition == null)
082                    {
083                        invalidContents.add(contentIdToMove);
084                        break;
085                    }
086                    
087                    // 1) As move towards the root node means removing the "parent" attribute, check it is not mandatory
088                    Validator validator = parentAttributeDefinition.getValidator();
089                    if (validator != null && (Boolean) validator.getConfiguration().get("mandatory"))
090                    {
091                        invalidContents.add(contentIdToMove);
092                        break;
093                    }
094                    
095                    // 2) Check the content is a potential child of root node, i.e. has good content type
096                    if (Arrays.asList(contentToMove.getTypes()).contains(topLevelType))
097                    {
098                        validContents.add(_validContentToMap(contentToMove, Collections.singletonList(topLevelType)));
099                    }
100                    else
101                    {
102                        invalidContents.add(contentIdToMove);
103                    }
104                }
105                catch (AmetysRepositoryException e)
106                {
107                    getLogger().error("An error occured while retrieving the content to move.", e);
108                    invalidContents.add(contentIdToMove);
109                }
110            }
111        }
112        else
113        {
114            Content newParent;
115            try
116            {
117                newParent = _resolver.resolveById(newParentContentId);
118            }
119            catch (AmetysRepositoryException e)
120            {
121                getLogger().error("An error occured while retrieving the new parent content.", e);
122                result.put("error", "unknown-parent");
123                return result;
124            }
125            List<String> childContentTypes = _hierarchicalSimpleContentsHelper.getChildContentTypes(newParent).stream()
126                                                                                                              .map(ContentType::getId)
127                                                                                                              .collect(Collectors.toList());
128            for (String contentIdToMove : contentIdsToMove)
129            {
130                try
131                {
132                    Content contentToMove = _resolver.resolveById(contentIdToMove);
133                    if (CollectionUtils.containsAny(Arrays.asList(contentToMove.getTypes()), childContentTypes))
134                    {
135                        // The content types of the content is valid with the new parent
136                        Map<String, String> validContent = _validContentToMap(contentToMove, childContentTypes);
137                        validContents.add(validContent);
138                    }
139                    else
140                    {
141                        invalidContents.add(contentIdToMove);
142                    }
143                }
144                catch (AmetysRepositoryException e)
145                {
146                    getLogger().error("An error occured while retrieving the content to move.", e);
147                    invalidContents.add(contentIdToMove);
148                }
149            }
150        }
151        
152        return result;
153    }
154    
155    private ContentAttributeDefinition _getParentAttributeDefinition(Content content)
156    {
157        for (String cTypeId : content.getTypes())
158        {
159            ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId);
160            Optional<ContentAttributeDefinition> parentAttribute = cType.getParentAttributeDefinition();
161            if (!parentAttribute.isEmpty())
162            {
163                return parentAttribute.get();
164            }
165        }
166        
167        return null;
168    }
169    
170    private Map<String, String> _validContentToMap(Content validContent, List<String> childContentTypes)
171    {
172        Map<String, String> contentMap = new HashMap<>();
173        contentMap.put("id", validContent.getId());
174        
175        List<String> types = Arrays.asList(validContent.getTypes());
176        for (String childContentTypeId : childContentTypes)
177        {
178            if (types.contains(childContentTypeId))
179            {
180                ContentType childContentType = _contentTypeExtensionPoint.getExtension(childContentTypeId);
181                childContentType.getParentAttributeDefinition()
182                    .ifPresent(parentAttribute -> contentMap.put("parentAttributeName", parentAttribute.getName()));
183            }
184        }
185        
186        contentMap.put("parentAttributeValue", _hierarchicalSimpleContentsHelper.getParent(validContent));
187        
188        return contentMap;
189    }
190}