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.runtime.model; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Comparator; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.TreeSet; 026 027import org.slf4j.Logger; 028 029import org.ametys.runtime.i18n.I18nizableText; 030 031/** 032 * Helper for {@link CategorizedElementDefinitionProxy} 033 */ 034public final class CategorizedElementDefinitionHelper 035{ 036 private CategorizedElementDefinitionHelper() 037 { 038 //Nothing 039 } 040 041 /** 042 * Get definitions of all elements parsed in this {@link CategorizedElementDefinitionProxy} {@link Collection}, as a flat map. 043 * @param elements a {@link Collection} of {@link CategorizedElementDefinitionProxy} 044 * @return a map with the definition name as key, containing all definitions 045 */ 046 public static Map<String, ElementDefinition> getFlatDefinitions(Collection<CategorizedElementDefinitionProxy> elements) 047 { 048 Map<String, ElementDefinition> flatDefinitions = new HashMap<>(); 049 for (CategorizedElementDefinitionProxy element : elements) 050 { 051 ElementDefinition definition = element.getDefinition(); 052 flatDefinitions.put(definition.getName(), definition); 053 } 054 return flatDefinitions; 055 } 056 057 /** 058 * Categorize the ModlItems based on the informations in the {@link CategorizedElementDefinitionProxy} collection 059 * Category and groups are not sorted, only definitions, based on the position. 060 * Sorting of category and groups have to be done when the view is created (each usage can be different) 061 * @param elements collection of categorized elements 062 * @return a list of {@link ModelItem} with the correct tree (category/group/definitions) 063 */ 064 public static List<ModelItem> categorize(Collection<CategorizedElementDefinitionProxy> elements) 065 { 066 Map<I18nizableText, Map<I18nizableText, List<CategorizedElementDefinitionProxy>>> categories = categorizeToMap(elements); 067 //This list is not sorted, only definition are sorted, not groups. 068 //Groups should be sorted elsewhere (when creating the view for example) 069 List<ModelItem> modelItems = new ArrayList<>(); 070 for (Entry<I18nizableText, Map<I18nizableText, List<CategorizedElementDefinitionProxy>>> categoryEntry : categories.entrySet()) 071 { 072 ModelItemGroup category = new ModelItemGroup(); 073 category.setLabel(categoryEntry.getKey()); 074 modelItems.add(category); 075 Map<I18nizableText, List<CategorizedElementDefinitionProxy>> values = categoryEntry.getValue(); 076 for (Entry<I18nizableText, List<CategorizedElementDefinitionProxy>> groupEntry : values.entrySet()) 077 { 078 ModelItemGroup group = new ModelItemGroup(); 079 group.setLabel(groupEntry.getKey()); 080 category.addChild(group); 081 082 Collection<CategorizedElementDefinitionProxy> orderedDefinitions = orderDefinitions(groupEntry.getValue()); 083 for (CategorizedElementDefinitionProxy orderedDefinition : orderedDefinitions) 084 { 085 ElementDefinition definition = orderedDefinition.getDefinition(); 086 definition.setParent(group); 087 088 group.addChild(definition, orderedDefinition.isGroupSwitch()); 089 } 090 } 091 } 092 return modelItems; 093 } 094 095 /** 096 * Organize a collection of skin parameters by categories and groups. 097 * @param elements a collection of elements. 098 * @return a Map of parameters sorted first by category then group. 099 */ 100 protected static Map<I18nizableText, Map<I18nizableText, List<CategorizedElementDefinitionProxy>>> categorizeToMap(Collection<CategorizedElementDefinitionProxy> elements) 101 { 102 Map<I18nizableText, Map<I18nizableText, List<CategorizedElementDefinitionProxy>>> categories = new HashMap<>(); 103 104 // Classify parameters by groups and categories 105 for (CategorizedElementDefinitionProxy parameter : elements) 106 { 107 I18nizableText displayCategory = parameter.getDisplayCategory(); 108 I18nizableText displayGroup = parameter.getDisplayGroup(); 109 110 // Get the map of groups of the category 111 Map<I18nizableText, List<CategorizedElementDefinitionProxy>> category = categories.get(displayCategory); 112 if (category == null) 113 { 114 category = new HashMap<>(); 115 categories.put(displayCategory, category); 116 } 117 118 // Get the map of parameters of the group 119 List<CategorizedElementDefinitionProxy> group = category.get(displayGroup); 120 if (group == null) 121 { 122 group = new ArrayList<>(); 123 category.put(displayGroup, group); 124 } 125 126 group.add(parameter); 127 } 128 129 return categories; 130 } 131 132 /** 133 * Order definitions, based on their position 134 * @param definitions {@link Collection} of {@link CategorizedElementDefinitionProxy} 135 * @return an ordered {@link Collection} of {@link CategorizedElementDefinitionProxy} 136 */ 137 protected static Collection<CategorizedElementDefinitionProxy> orderDefinitions(Collection<CategorizedElementDefinitionProxy> definitions) 138 { 139 return orderDefinitions(definitions, null, null); 140 } 141 /** 142 * Order definitions, based on their position 143 * @param definitions {@link Collection} of {@link CategorizedElementDefinitionProxy} 144 * @param categoryLabel label of the category to filter (null to avoid filter) 145 * @param groupLabel label of the group (null to avoid filter) 146 * @return an ordered {@link Collection} of {@link CategorizedElementDefinitionProxy} 147 */ 148 protected static Collection<CategorizedElementDefinitionProxy> orderDefinitions(Collection<CategorizedElementDefinitionProxy> definitions, I18nizableText categoryLabel, I18nizableText groupLabel) 149 { 150 Collection<CategorizedElementDefinitionProxy> orderedParameters = new TreeSet<>(new Comparator<CategorizedElementDefinitionProxy>() 151 { 152 public int compare(CategorizedElementDefinitionProxy o1, CategorizedElementDefinitionProxy o2) 153 { 154 int positionComparison = ((Long) o1.getPosition()).compareTo(o2.getPosition()); 155 if (o1.getPosition() < 0 && o2.getPosition() < 0) 156 { 157 positionComparison = 0; 158 } 159 else if (o1.getPosition() < 0 && o2.getPosition() >= 0) 160 { 161 positionComparison = 1; 162 } 163 else if (o2.getPosition() < 0) 164 { 165 positionComparison = -1; 166 } 167 168 if (positionComparison != 0) 169 { 170 return positionComparison; 171 } 172 return o1.getDefinition().compareTo(o2.getDefinition()); 173 } 174 }); 175 for (CategorizedElementDefinitionProxy parameterProxy : definitions) 176 { 177 boolean areCategoryAndGroupCoherent = true; 178 if (categoryLabel != null) 179 { 180 areCategoryAndGroupCoherent &= categoryLabel.equals(parameterProxy.getDisplayCategory()); 181 } 182 if (groupLabel != null) 183 { 184 areCategoryAndGroupCoherent &= groupLabel.equals(parameterProxy.getDisplayGroup()); 185 } 186 if (areCategoryAndGroupCoherent) 187 { 188 orderedParameters.add(parameterProxy); 189 } 190 } 191 return orderedParameters; 192 } 193 194 /** 195 * Validate parameters before writing 196 * @param definitionAndValues a map of all parameters and their values 197 * @param flatDefinitions flat definition of {@link ElementDefinition} from the model file 198 * @param logger a logger 199 * @return a map containing the potential errors 200 * @deprecated TODO NEWATTRIBUTEAPI_CONFIG RUNTIME-2851 remove this method when it is not necessary anymore to have the flat definitions 201 */ 202 @Deprecated 203 public static Map<String, List<I18nizableText>> validateValuesForWriting (Map<String, DefinitionAndValue> definitionAndValues, Map<String, ElementDefinition> flatDefinitions, Logger logger) 204 { 205 Map<String, List<I18nizableText>> errorFields = new HashMap<>(); 206 207 for (ElementDefinition definition : flatDefinitions.values()) 208 { 209 boolean isGroupSwitchOn = ModelHelper.isGroupSwitchOn(definition, DefinitionAndValue.extractValues(definitionAndValues)); 210 boolean isDisabled = ModelHelper.evaluateDisableConditions(definition.getDisableConditions(), definitionAndValues, logger); 211 212 if (isGroupSwitchOn && !isDisabled) 213 { 214 Object value = null; 215 DefinitionAndValue definitionAndValue = definitionAndValues.get(definition.getName()); 216 if (definitionAndValue != null) 217 { 218 value = definitionAndValue.getValue(); 219 } 220 221 List<I18nizableText> errors = ModelHelper.validateValue(definition, value); 222 223 if (!errors.isEmpty()) 224 { 225 logger.warn("The configuration parameter '{}' is not valid", definition.getName()); 226 errorFields.put(definition.getName(), errors); 227 } 228 } 229 } 230 231 if (!errorFields.isEmpty()) 232 { 233 logger.debug("Failed to save configuration because of invalid parameter values"); 234 } 235 return errorFields; 236 } 237 238 /** 239 * Validate parameters before writing 240 * @param definitionAndValues a map of all parameters and their values 241 * @param logger a logger 242 * @return a map containing the potential errors 243 */ 244 public static Map<String, List<I18nizableText>> validateValuesForWriting (Map<String, DefinitionAndValue> definitionAndValues, Logger logger) 245 { 246 Map<String, List<I18nizableText>> errorFields = new HashMap<>(); 247 for (Entry<String, DefinitionAndValue> entry : definitionAndValues.entrySet()) 248 { 249 DefinitionAndValue definitionAndValue = entry.getValue(); 250 ModelItem definition = definitionAndValue.getDefinition(); 251 if (definition instanceof ElementDefinition) 252 { 253 ElementDefinition elementDefinition = (ElementDefinition) definition; 254 boolean isGroupSwitchOn = ModelHelper.isGroupSwitchOn(elementDefinition, DefinitionAndValue.extractValues(definitionAndValues)); 255 boolean isDisabled = ModelHelper.evaluateDisableConditions(elementDefinition.getDisableConditions(), definitionAndValues, logger); 256 257 if (isGroupSwitchOn && !isDisabled) 258 { 259 Object value = definitionAndValue.getValue(); 260 261 List<I18nizableText> errors = ModelHelper.validateValue(elementDefinition, value); 262 263 if (!errors.isEmpty()) 264 { 265 logger.warn("The configuration parameter '{}' is not valid", elementDefinition.getName()); 266 errorFields.put(elementDefinition.getName(), errors); 267 } 268 } 269 } 270 } 271 272 if (!errorFields.isEmpty()) 273 { 274 logger.debug("Failed to save configuration because of invalid parameter values"); 275 } 276 return errorFields; 277 } 278 279 /** 280 * Generate the wiew for a list of {@link ModelItem}, using comparators to sort the categories and the groups 281 * This works only on the very specific categories/group/fieldset hierarchy and an {@link IllegalArgumentException} will be thrown if the hierarchy is incorrect. 282 * @param categories list of {@link ModelItem} 283 * @param categoriesComparator {@link Comparator} used for the categories (can be null to keep order) 284 * @param groupsComparator {@link Comparator} for the groups (can be null to keep order) 285 * @return A {@link View} ordered from the {@link ModelItem} using the {@link Comparator} 286 * @throws IllegalArgumentException the hierarchy of the categories/groups/elements is incorrect 287 */ 288 public static View buildViewFromCategories(Collection<? extends ModelItem> categories, Comparator<? super ModelItem> categoriesComparator, Comparator<? super ModelItem> groupsComparator) throws IllegalArgumentException 289 { 290 View view = new View(); 291 292 Collection<? extends ModelItem> sorted = _sort(categories, categoriesComparator); 293 294 for (ModelItem modelItem : sorted) 295 { 296 view.addViewItem(_buildCategoryViewItem(modelItem, groupsComparator)); 297 } 298 return view; 299 } 300 301 /** 302 * sort a model item list using comparator 303 * @param items list of items to sort 304 * @param comparator a comparator, can be null to avoid sort 305 * @return a sorted list (or a copy of the list if the comparator is null) 306 */ 307 private static Collection<? extends ModelItem> _sort(Collection<? extends ModelItem> items, Comparator<? super ModelItem> comparator) 308 { 309 if (comparator == null) 310 { 311 return items; 312 } 313 else 314 { 315 List<ModelItem> sorted = new ArrayList<>(items); 316 sorted.sort(comparator); 317 return sorted; 318 } 319 } 320 321 private static ModelViewItemGroup _buildCategoryViewItem(ModelItem category, Comparator<? super ModelItem> groupsComparator) throws IllegalArgumentException 322 { 323 if (category instanceof ModelItemGroup) 324 { 325 ModelItemGroup modelGroup = (ModelItemGroup) category; 326 ModelViewItemGroup categoryViewItem = new ModelViewItemGroup(); 327 categoryViewItem.setRole(ViewItemGroup.TAB_ROLE); 328 categoryViewItem.setDefinition(modelGroup); 329 330 Collection<? extends ModelItem> groups = _sort(modelGroup.getChildren(), groupsComparator); 331 332 for (ModelItem group : groups) 333 { 334 categoryViewItem.addViewItem(_buildGroupViewItem(group)); 335 } 336 return categoryViewItem; 337 } 338 else 339 { 340 throw new IllegalArgumentException("Category " + category.getPath() + " should be an instance of ModelItemGroup"); 341 } 342 } 343 344 private static ModelViewItemGroup _buildGroupViewItem(ModelItem modelItem) throws IllegalArgumentException 345 { 346 if (modelItem instanceof ModelItemGroup) 347 { 348 ModelItemGroup modelGroup = (ModelItemGroup) modelItem; 349 ModelViewItemGroup groupViewItem = new ModelViewItemGroup(); 350 groupViewItem.setRole(ViewItemGroup.FIELDSET_ROLE); 351 groupViewItem.setDefinition(modelGroup); 352 List<ModelItem> items = modelGroup.getChildren(); // They are already sorted 353 for (ModelItem item : items) 354 { 355 groupViewItem.addViewItem(_buildElementViewItem(item)); 356 } 357 return groupViewItem; 358 } 359 else 360 { 361 throw new IllegalArgumentException("Group " + modelItem.getPath() + " should be an instance of ModelItemGroup"); 362 } 363 } 364 365 private static ModelViewItem _buildElementViewItem(ModelItem modelItem) throws IllegalArgumentException 366 { 367 if (modelItem instanceof ElementDefinition) 368 { 369 ElementDefinition definition = (ElementDefinition) modelItem; 370 371 ViewElement parameterViewItem = new ViewElement(); 372 parameterViewItem.setDefinition(definition); 373 return parameterViewItem; 374 } 375 else 376 { 377 throw new IllegalArgumentException("Item " + modelItem.getPath() + " should be an instance of ElementDefinition"); 378 } 379 } 380}