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.odf.validator;
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.Stream;
025
026import org.apache.avalon.framework.configuration.Configurable;
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.ConfigurationException;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032
033import org.ametys.cms.contenttype.validation.AbstractContentValidator;
034import org.ametys.cms.data.ContentValue;
035import org.ametys.cms.repository.Content;
036import org.ametys.odf.ODFHelper;
037import org.ametys.odf.ProgramItem;
038import org.ametys.odf.course.Course;
039import org.ametys.odf.courselist.CourseList;
040import org.ametys.odf.program.ProgramPart;
041import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper;
042import org.ametys.runtime.i18n.I18nizableText;
043import org.ametys.runtime.i18n.I18nizableTextParameter;
044import org.ametys.runtime.model.ElementDefinition;
045import org.ametys.runtime.model.View;
046import org.ametys.runtime.parameter.Errors;
047
048/**
049 * Global validator for {@link ProgramItem} content.
050 * Check that structure to not create an infinite loop
051 */
052public class ProgramItemHierarchyValidator extends AbstractContentValidator implements Serviceable, Configurable
053{
054    /** The ODF helper */
055    protected ODFHelper _odfHelper;
056    
057    private Set<String> _childMetadataNames;
058    
059    @Override
060    public void service(ServiceManager smanager) throws ServiceException
061    {
062        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
063    }
064    
065    @Override
066    public void validate(Content content, Errors errors)
067    {
068        // Nothing
069    }
070    
071    public void configure(Configuration configuration) throws ConfigurationException
072    {
073        _childMetadataNames = new HashSet<>();
074        
075        Configuration[] children = configuration.getChildren("childMetadata");
076        for (Configuration childConf : children)
077        {
078            _childMetadataNames.add(childConf.getAttribute("name"));
079        }
080    }
081    
082    @Override
083    public void validate(Content content, Map<String, Object> values, View view, Errors errors)
084    {
085        Set<String> names = getChildMetadataNames();
086        for (String name : names)
087        {
088            if (view.hasModelViewItem(name) && content instanceof ProgramItem)
089            {
090                ElementDefinition childDefinition = (ElementDefinition) content.getDefinition(name);
091                Object[] valueToValidate = (Object[]) DataHolderHelper.getValueToValidate(values.get(name));
092                
093                ContentValue[] contentValues = Stream.of(valueToValidate).map(v -> childDefinition.getType().castValue(v)).toArray(ContentValue[]::new);
094                for (ContentValue contentValue : contentValues)
095                {
096                    Content childContent = contentValue.getContent();
097                    
098                    if (childContent instanceof ProgramItem)
099                    {
100                        if (!checkAncestors((ProgramItem) content, (ProgramItem) childContent))
101                        {
102                            addHierarchyError(errors, childDefinition, content, childContent);
103                        }
104                    }
105                }
106            }
107        }
108    }
109    
110    /**
111     * Get the names of child metadata to be checked
112     * @return the child metadata's names
113     */
114    protected Set<String> getChildMetadataNames()
115    {
116        return _childMetadataNames;
117    }
118    
119    /**
120     * Check if the hierarchy of a program item will be still valid if adding the given program item as child.
121     * Return false if the {@link ProgramItem} to add is in the hierarchy of the given {@link ProgramItem}
122     * @param programItem The content to start search
123     * @param childProgramItem The child program item to search in ancestors
124     * @return true if child program item is already part of the hierarchy (ascendant search)
125     */
126    protected boolean checkAncestors(ProgramItem programItem, ProgramItem childProgramItem)
127    {
128        if (programItem.equals(childProgramItem))
129        {
130            return false;
131        }
132
133        List<? extends ProgramItem> parents = new ArrayList<>();
134        if (programItem instanceof Course)
135        {
136            parents = ((Course) programItem).getParentCourseLists();
137        }
138        else if (programItem instanceof CourseList)
139        {
140            parents = ((CourseList) programItem).getParentCourses();
141        }
142        else if (programItem instanceof ProgramPart)
143        {
144            parents = ((ProgramPart) programItem).getProgramPartParents();
145        }
146        
147        for (ProgramItem parent : parents)
148        {
149            if (parent.equals(childProgramItem))
150            {
151                return false;
152            }
153            else if (!checkAncestors(parent, childProgramItem))
154            {
155                return false;
156            }
157        }
158        
159        return true;
160    }
161    
162    /**
163     * Add an error the ascendant hierarchy is invalid (infinite loop)
164     * @param errors The list of errors
165     * @param childDefinition The child metadata definition
166     * @param content The content being edited
167     * @param childContent The content to add as child content
168     */
169    protected void addHierarchyError(Errors errors, ElementDefinition childDefinition, Content content, Content childContent)
170    {
171        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
172        i18nParams.put("title", new I18nizableText(content.getTitle()));
173        i18nParams.put("childTitle", new I18nizableText(childContent.getTitle()));
174        i18nParams.put("fieldLabel", childDefinition.getLabel());
175        errors.addError(new I18nizableText("plugin.odf", "PLUGINS_ODF_CONTENT_VALIDATOR_HIERARCHY_ERROR", i18nParams));
176    }
177}