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}