001/*
002 *  Copyright 2020 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.repository.model;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023import java.util.function.BiConsumer;
024import java.util.function.Consumer;
025
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.runtime.i18n.I18nizableText;
029import org.ametys.runtime.model.ElementDefinition;
030import org.ametys.runtime.model.ModelHelper;
031import org.ametys.runtime.model.ModelItem;
032import org.ametys.runtime.model.ModelItemGroup;
033import org.ametys.runtime.model.ModelViewItemGroup;
034import org.ametys.runtime.model.ViewElement;
035import org.ametys.runtime.model.ViewItem;
036import org.ametys.runtime.model.ViewItemAccessor;
037import org.ametys.runtime.model.ViewItemGroup;
038
039/**
040 * Helper for manipulating views in the context of the repository plugin (aware of repeaters, composites, ...).
041 */
042public final class ViewHelper
043{
044    private ViewHelper()
045    {
046        // Empty constructor
047    }
048    
049    /**
050     * Visit a view, allowing to perform specific actions for view elements.
051     * @param viewItemAccessor the {@link ViewItemAccessor} to visit.
052     * @param elementConsumer the consumer called on each {@link ViewElement}.
053     * @param compositeConsumer the consumer called on each item refering to a {@link CompositeDefinition}.
054     * @param repeaterConsumer the consumer called on each item refering to a {@link RepeaterDefinition}.
055     * @param groupConsumer the consumer called on each other {@link ViewItemGroup}.
056     */
057    public static void visitView(ViewItemAccessor viewItemAccessor, BiConsumer<ViewElement, ElementDefinition> elementConsumer, BiConsumer<ModelViewItemGroup, CompositeDefinition> compositeConsumer, BiConsumer<ModelViewItemGroup, RepeaterDefinition> repeaterConsumer, Consumer<ViewItemGroup> groupConsumer)
058    {
059        for (ViewItem viewItem : viewItemAccessor.getViewItems())
060        {
061            if (viewItem instanceof ViewElement)
062            {
063                ElementDefinition definition = ((ViewElement) viewItem).getDefinition();
064                elementConsumer.accept((ViewElement) viewItem, definition);
065            }
066            else if (viewItem instanceof ModelViewItemGroup)
067            {
068                ModelItemGroup modelItemGroup = ((ModelViewItemGroup) viewItem).getDefinition();
069                
070                if (modelItemGroup instanceof CompositeDefinition)
071                {
072                    compositeConsumer.accept((ModelViewItemGroup) viewItem, (CompositeDefinition) modelItemGroup);
073                }
074                else if (modelItemGroup instanceof RepeaterDefinition)
075                {
076                    repeaterConsumer.accept((ModelViewItemGroup) viewItem, (RepeaterDefinition) modelItemGroup);
077                }
078            }
079            else if (viewItem instanceof ViewItemGroup)
080            {
081                groupConsumer.accept((ViewItemGroup) viewItem);
082            }
083        }
084    }
085    
086    /**
087     * Validates the given values according to the view item accessor
088     * @param values the values to validate
089     * @param viewItemAccessor the view item accessor to visit
090     * @return the errors information if the validation fails.
091     */
092    public static Map<String, List<I18nizableText>> validateValues(ViewItemAccessor viewItemAccessor, Optional<Map<String, Object>> values)
093    {
094        return _validateValues(viewItemAccessor, values, StringUtils.EMPTY);
095    }
096    
097    private static Map<String, List<I18nizableText>> _validateValues(ViewItemAccessor viewItemAccessor, Optional<Map<String, Object>> values, String prefix)
098    {
099        Map<String, List<I18nizableText>> allErrors = new HashMap<>();
100        
101        visitView(viewItemAccessor, 
102            (element, definition) -> {
103                // simple element
104                String name = definition.getName();
105                String dataPath = prefix + name;
106                
107                Object value = values.map(v -> v.get(name)).orElse(null);
108                List<I18nizableText> errors = ModelHelper.validateValue(definition, value);
109                
110                if (!errors.isEmpty())
111                {
112                    allErrors.put(dataPath, errors);
113                }
114            }, 
115            (group, definition) -> {
116                // composite
117                String name = definition.getName();
118                String updatedPrefix = prefix + name + ModelItem.ITEM_PATH_SEPARATOR;
119
120                Optional<Map<String, Object>> value = values.map(v -> v.get(name)).filter(Map.class::isInstance).map(Map.class::cast);
121                allErrors.putAll(_validateValues(group, value, updatedPrefix));
122            }, 
123            (group, definition) -> {
124                // repeater
125                String name = definition.getName();
126                String dataPath = prefix + name;
127                
128                Optional<List<Map<String, Object>>> entries = values.map(v -> v.get(name)).filter(List.class::isInstance).map(List.class::cast);
129                allErrors.putAll(_validateRepeaterEntries(group, definition, entries, dataPath));
130            }, 
131            group -> {
132                allErrors.putAll(_validateValues(group, values, prefix));   
133            });
134        
135        return allErrors;
136    }
137    
138    private static Map<String, List<I18nizableText>> _validateRepeaterEntries(ModelViewItemGroup viewItem, RepeaterDefinition definition, Optional<List<Map<String, Object>>> entries, String dataPath)
139    {
140        Map<String, List<I18nizableText>> repeaterErrors = new HashMap<>();
141
142        int repeaterSize = entries.map(List::size).orElse(0);
143        List<I18nizableText> repeaterSizeErrors = _validateRepeaterSize(definition, repeaterSize);
144        if (!repeaterSizeErrors.isEmpty())
145        {
146            repeaterErrors.put(dataPath, repeaterSizeErrors);
147        }
148        else if (entries.isPresent())
149        {
150            for (int i = 0; i < repeaterSize; i++)
151            {
152                Map<String, Object> entry = entries.get().get(i);
153                String prefix = dataPath + "[" + (i + 1)  + "]" + ModelItem.ITEM_PATH_SEPARATOR;
154                repeaterErrors.putAll(_validateValues(viewItem, Optional.of(entry), prefix));
155            }
156        }
157        
158        return repeaterErrors;
159    }
160
161    private static List<I18nizableText> _validateRepeaterSize(RepeaterDefinition definition, int repeaterSize)
162    {
163        int minSize = definition.getMinSize();
164        int maxSize = definition.getMaxSize();
165        
166        List<I18nizableText> errors = new ArrayList<>();
167        
168        if (repeaterSize < minSize)
169        {
170            List<String> parameters = new ArrayList<>();
171            parameters.add(definition.getName());
172            parameters.add(Integer.toString(minSize));
173            errors.add(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_REPEATER_MINSIZE", parameters));
174        }
175
176        if (maxSize > 0 && repeaterSize > maxSize)
177        {
178            List<String> parameters = new ArrayList<>();
179            parameters.add(definition.getName());
180            parameters.add(Integer.toString(maxSize));
181            errors.add(new I18nizableText("plugin.cms", "CONTENT_EDITION_VALIDATION_ERRORS_REPEATER_MAXSIZE", parameters));
182        }
183        
184        return errors;
185    }
186}