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