001/*
002 *  Copyright 2023 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.Collection;
019import java.util.List;
020import java.util.Optional;
021import java.util.regex.Matcher;
022import java.util.regex.Pattern;
023
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.commons.lang3.StringUtils;
028
029import org.ametys.runtime.i18n.I18nizableText;
030import org.ametys.runtime.model.ItemParserHelper.ConfigurationAndPluginName;
031import org.ametys.runtime.model.ModelViewItem.DisabledItemRendering;
032import org.ametys.runtime.model.ViewHelper.InsertMode;
033import org.ametys.runtime.model.exception.UndefinedItemPathException;
034
035/**
036 * Abstract component that parses view's configuration
037 */
038public abstract class AbstractViewParser extends AbstractLogEnabled implements ViewParser
039{
040    /** The regex to remove view references in model item references */
041    protected static final String __VIEW_REFERENCE_REGEX = "\\[(.+)\\]";
042    /** The pattern to find view references in model item references */
043    protected static final Pattern __VIEW_REFERENCE_PATTERN = Pattern.compile("^[^\\[]+" + __VIEW_REFERENCE_REGEX + "$");
044    
045    public View parseView(ConfigurationAndPluginName viewConfiguration, ViewParserContext context) throws ConfigurationException
046    {
047        View view = new View();
048        view.setName(_parseViewName(viewConfiguration));
049        
050        // Get the model
051        Collection<? extends Model> model = _getModel();
052        
053        // Parse general information of the view Configuration
054        _fillViewGeneralInformation(viewConfiguration, view, view, model);
055        
056        // Parse the view items of the view Configuration
057        for (Configuration itemConfiguration : viewConfiguration.configuration().getChildren())
058        {
059            _parseViewChild(new ConfigurationAndPluginName(itemConfiguration, viewConfiguration.pluginName(), viewConfiguration.defaultI18nCatalog()), view, model, false, context);
060        }
061
062        return view;
063    }
064    
065    /**
066     * Parses the name of the view
067     * @param viewConfiguration the view's configuration 
068     * @return the view's name
069     * @throws ConfigurationException if an error occurs while parsing the view's name
070     */
071    protected String _parseViewName(ConfigurationAndPluginName viewConfiguration) throws ConfigurationException
072    {
073        return viewConfiguration.configuration().getAttribute("name");
074    }
075    
076    public View overrideView(ConfigurationAndPluginName viewConfiguration, View existingView, ViewParserContext context) throws ConfigurationException
077    {
078        View view = new View();
079        view.setName(existingView.getName());
080        
081        // Get the model
082        Collection<? extends Model> model = _getModel();
083        
084        // Parse general information of the view Configuration
085        _fillViewGeneralInformation(viewConfiguration, view, existingView, model);
086        
087        // Parse the view items of the view Configuration
088        view.addViewItems(existingView.getViewItems());
089        for (Configuration itemConfiguration : viewConfiguration.configuration().getChildren())
090        {
091            _parseViewChild(new ConfigurationAndPluginName(itemConfiguration, viewConfiguration.pluginName(), viewConfiguration.defaultI18nCatalog()), view, model, true, context);
092        }
093
094        return view;
095    }
096    
097    /**
098     * Retrieves the model corresponding to the view to parse
099     * @return the model
100     */
101    protected abstract Collection<? extends Model> _getModel();
102    
103    /**
104     * Fill the general information of the given view (label, description, ...)
105     * @param viewConfiguration the configuration of the view to fill
106     * @param view the view to fill
107     * @param existingView the existing view, that may already contain general information
108     * @param model The model of the view
109     * @throws ConfigurationException if the configuration is not valid.
110     */
111    protected void _fillViewGeneralInformation(ConfigurationAndPluginName viewConfiguration, View view, View existingView, Collection<? extends Model> model) throws ConfigurationException
112    {
113        // Internal
114        view.setInternal(viewConfiguration.configuration().getAttributeAsBoolean("internal", existingView.isInternal()));
115        
116        // Label
117        I18nizableText label = existingView.getLabel() == null
118                ? ItemParserHelper.parseI18nizableText(viewConfiguration, "label", existingView.getName())
119                : ItemParserHelper.parseI18nizableText(viewConfiguration, "label", existingView.getLabel());  // Case of override, use the existing view as default value
120        view.setLabel(label);
121        
122        // Description
123        I18nizableText description = existingView.getDescription() == null
124                ? ItemParserHelper.parseI18nizableText(viewConfiguration, "description") // default value is an empty string
125                : ItemParserHelper.parseI18nizableText(viewConfiguration, "description", existingView.getDescription());  // Case of override, use the existing view as default value
126        view.setDescription(description);
127    }
128    
129    /**
130     * Parses the item with the given configuration and add the item to the given view
131     * @param itemConfiguration the configuration of the item to parse
132     * @param view the view
133     * @param model The model containing the parsed item
134     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
135     * @param context the context of the view parsing
136     * @throws ConfigurationException if the configuration is not valid.
137     */
138    protected void _parseViewChild(ConfigurationAndPluginName itemConfiguration, View view, Collection<? extends Model> model, boolean override, ViewParserContext context) throws ConfigurationException
139    {
140        if (_isAddingItemConfiguration(itemConfiguration.configuration()))
141        {
142            _parseModelViewItem(itemConfiguration, view, model, view, override, context);
143        }
144        else if (_isRemovingItemConfiguration(itemConfiguration.configuration()))
145        {
146            _removeViewItemFromView(view, itemConfiguration.configuration());
147        }
148        else if (_isAddingGroupConfiguration(itemConfiguration.configuration()))
149        {
150            _parseSimpleViewItemGroup(itemConfiguration, ViewItemGroup.TAB_ROLE, view, model, view, override, context);
151        }
152    }
153    
154    /**
155     * Checks if the given item configuration is an adding item configuration
156     * @param itemConfiguration the item configuration
157     * @return <code>true</code> if the given item configuration is an adding item configuration, <code>false</code> otherwise
158     */
159    protected boolean _isAddingItemConfiguration(Configuration itemConfiguration)
160    {
161        String itemConfigurationName = itemConfiguration.getName();
162        return ADD_ITEM_TAG_NAME.equals(itemConfigurationName);
163    }
164    
165    /**
166     * Checks if the given item configuration is a removing item configuration
167     * @param itemConfiguration the item configuration
168     * @return <code>true</code> if the given item configuration is a removing item configuration, <code>false</code> otherwise
169     */
170    protected boolean _isRemovingItemConfiguration(Configuration itemConfiguration)
171    {
172        String itemConfigurationName = itemConfiguration.getName();
173        return REMOVE_ITEM_TAG_NAME.equals(itemConfigurationName);
174    }
175    
176    /**
177     * Checks if the given item configuration is an adding group configuration
178     * @param itemConfiguration the item configuration
179     * @return <code>true</code> if the given item configuration is an adding group configuration, <code>false</code> otherwise
180     */
181    protected boolean _isAddingGroupConfiguration(Configuration itemConfiguration)
182    {
183        String itemConfigurationName = itemConfiguration.getName();
184        return ADD_GROUP_TAG_NAME.equals(itemConfigurationName);
185    }
186    
187    /**
188     * Parses a model view item and add it to its parent view item accessor
189     * @param itemConfiguration configuration of the model view item
190     * @param parentViewItemAccessor the parent view item accessor of the model view item to parse
191     * @param parentModelItemAccessors the parents model item accessors of the model item corresponding to the model view item to parse
192     * @param referenceView view that references the item
193     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
194     * @param context the context of the view parsing
195     * @throws ConfigurationException if the configuration is not valid.
196     */
197    protected void _parseModelViewItem(ConfigurationAndPluginName itemConfiguration, ViewItemAccessor parentViewItemAccessor, Collection<? extends ModelItemAccessor> parentModelItemAccessors, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
198    {
199        String modelItemReference = _getModelItemReference(itemConfiguration.configuration());
200        
201        Matcher viewReferenceMatcher = __VIEW_REFERENCE_PATTERN.matcher(modelItemReference);
202        boolean hasViewReference = viewReferenceMatcher.matches();
203        
204        String modelItemPath = hasViewReference ? modelItemReference.replaceAll(__VIEW_REFERENCE_REGEX, StringUtils.EMPTY) : modelItemReference; // Remove potential view reference
205        
206        int lastIndexOfItemPathSeparator = !hasViewReference
207                // Resolve model item reference and initialize parents if the reference is a path
208                ? modelItemPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR)
209                // For view references, the model item is created as a parent view item accessor
210                : modelItemPath.length();
211        
212        
213        ViewItemAccessor finalParentViewItemAccessor = parentViewItemAccessor;
214        Collection<? extends ModelItemAccessor> finalParentModelItemAccessors = parentModelItemAccessors;
215        
216        String modelItemName = modelItemPath;
217        
218        if (lastIndexOfItemPathSeparator > -1)
219        {
220            // Get or create the view item accessors associated to the model item path prefix
221            String parentRelativePath = modelItemPath.substring(0, lastIndexOfItemPathSeparator);
222            finalParentViewItemAccessor = _createViewItemAccessor(itemConfiguration.configuration(), parentRelativePath, parentViewItemAccessor, parentModelItemAccessors, referenceView, override);
223            finalParentModelItemAccessors = List.of((ModelItemAccessor) ((ModelViewItem) finalParentViewItemAccessor).getDefinition());
224
225            modelItemName = !hasViewReference ? modelItemPath.substring(lastIndexOfItemPathSeparator + ModelItem.ITEM_PATH_SEPARATOR.length()) : StringUtils.EMPTY;
226        }
227        
228        if (hasViewReference)
229        {
230            String viewName = viewReferenceMatcher.group(1);
231            _parseViewReference(viewName, finalParentViewItemAccessor, finalParentModelItemAccessors.iterator().next());
232        }
233        else if (ALL_ITEMS_REFERENCE.equals(modelItemName))
234        {
235            parseAllModelViewItems(itemConfiguration, finalParentViewItemAccessor, finalParentModelItemAccessors, referenceView, override, context);
236        }
237        else
238        {
239            // Get the model item
240            ModelItem modelItem  = _getModelItem(itemConfiguration, modelItemName, finalParentModelItemAccessors);
241            
242            // Create the view item corresponding to the model item
243            ModelViewItem viewItem = createModelViewItem(itemConfiguration, modelItem, referenceView, override, context);
244    
245            // Add the view item to its parent
246            _addItemToViewItemAccessor(finalParentViewItemAccessor, viewItem, itemConfiguration.configuration(), referenceView, override);
247        }
248    }
249    
250    /**
251     * Parses the view reference to add items of the view to the given item accessor
252     * @param viewName the name of the referenced view
253     * @param viewItemAccessor the view item accessor referencing the view
254     * @param definition the definition of the model item containing the referenced view
255     * @throws ConfigurationException if the configuration is not valid
256     */
257    protected abstract void _parseViewReference(String viewName, ViewItemAccessor viewItemAccessor, ModelItemAccessor definition) throws ConfigurationException;
258
259    /**
260     * Parses all the model view items of the given parents
261     * @param itemConfiguration the item configuration
262     * @param parentViewItemAccessor the parent view item accessor
263     * @param parentModelItemAccessors the parent model item accorssors
264     * @param referenceView view that references the item
265     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
266     * @param context the context of the view parsing
267     * @throws ConfigurationException if the configuration is not valid.
268     */
269    protected void parseAllModelViewItems(ConfigurationAndPluginName itemConfiguration, ViewItemAccessor parentViewItemAccessor, Collection<? extends ModelItemAccessor> parentModelItemAccessors, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
270    {
271        // For all accessors' items 
272        for (ModelItem modelItem : ModelHelper.getModelItems(parentModelItemAccessors))
273        {
274            // Create the view item corresponding to the model item
275            ModelViewItem viewItem = createModelViewItemForAllItemsReference(itemConfiguration, modelItem, referenceView, override, context);
276      
277            // Add the view item to its parent
278            _addItemToViewItemAccessor(parentViewItemAccessor, viewItem, itemConfiguration.configuration(), referenceView, override);
279            
280            // Recursively add all item's children
281            if (modelItem instanceof ModelItemContainer modelItemContainer && viewItem instanceof ViewItemContainer viewItemContainer)
282            {
283                parseAllModelViewItems(itemConfiguration, viewItemContainer, List.of(modelItemContainer), referenceView, override, context);
284            }
285        }
286    }
287    
288    /**
289     * Creates the model view item corresponding to the given configuration, in cas of all items references
290     * @param itemConfiguration configuration of the model view item
291     * @param modelItem the model item
292     * @param referenceView view that references the item
293     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
294     * @param context the context of the view parsing
295     * @return the created model view item
296     * @throws ConfigurationException if the configuration is not valid.
297     */
298    protected ModelViewItem createModelViewItemForAllItemsReference(ConfigurationAndPluginName itemConfiguration, ModelItem modelItem, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
299    {
300        return createModelViewItem(itemConfiguration, modelItem, referenceView, override, context);
301    }
302
303    /**
304     * Creates the model view item corresponding to the given configuration
305     * @param itemConfiguration configuration of the model view item
306     * @param modelItem the model item
307     * @param referenceView view that references the item
308     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
309     * @param context the context of the view parsing
310     * @return the created model view item
311     * @throws ConfigurationException if the configuration is not valid.
312     */
313    protected ModelViewItem createModelViewItem(ConfigurationAndPluginName itemConfiguration, ModelItem modelItem, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
314    {
315        ModelViewItem viewItem;
316        if (modelItem instanceof ModelItemGroup modelItemGroup)
317        {
318            viewItem = _createLeafModelViewItemInstance(modelItemGroup);
319            for (Configuration childConfiguration : itemConfiguration.configuration().getChildren())
320            {
321                _parseViewItemAccessorChild(new ConfigurationAndPluginName(childConfiguration, itemConfiguration.pluginName(), itemConfiguration.defaultI18nCatalog()), (ViewItemAccessor) viewItem, modelItemGroup, referenceView, override, context);
322            }
323        }
324        else
325        {
326            viewItem = _parseViewElement(itemConfiguration, (ElementDefinition) modelItem, referenceView, override, context);
327        }
328        
329        // Parse label and description
330        if (itemConfiguration.configuration().getChild("label", false) != null)
331        {
332            viewItem.setLabel(ItemParserHelper.parseI18nizableText(itemConfiguration, "label"));
333        }
334        if (itemConfiguration.configuration().getChild("description", false) != null)
335        {
336            viewItem.setDescription(ItemParserHelper.parseI18nizableText(itemConfiguration, "description"));
337        }
338        
339        // Parse disabled item rendering
340        Optional.of(itemConfiguration)
341                .map(ConfigurationAndPluginName::configuration)
342                .map(config -> config.getAttribute("disabled", DisabledItemRendering.DEFAULT.name()))
343                .map(String::toUpperCase)
344                .map(DisabledItemRendering::valueOf)
345                .ifPresent(viewItem::setDisabledItemRendering);
346        
347        return viewItem;
348    }
349    
350    /**
351     * Creates view items corresponding to the given path.
352     * @param itemConfiguration the configuration containing the model item reference
353     * @param modelItemAccessorPath The path of the model item accessor to retrieve 
354     * @param viewItemAccessor the view item accessor relative to the given path 
355     * @param modelItemAccessors the model item accessor relative to the given path
356     * @param referenceView the reference view for includes
357     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
358     * @return the found or created view item accessor
359     * @throws ConfigurationException if the given path does not correspond to a model item accessor
360     */
361    protected ViewItemAccessor _createViewItemAccessor(Configuration itemConfiguration, String modelItemAccessorPath, ViewItemAccessor viewItemAccessor, Collection<? extends ModelItemAccessor> modelItemAccessors, View referenceView, boolean override) throws ConfigurationException
362    {
363        int firstIndexOfItemPathSeparator = modelItemAccessorPath.indexOf(ModelItem.ITEM_PATH_SEPARATOR);
364        String firstPathSegment = firstIndexOfItemPathSeparator > -1 ? modelItemAccessorPath.substring(0, modelItemAccessorPath.indexOf(ModelItem.ITEM_PATH_SEPARATOR)) : modelItemAccessorPath;
365        
366        ModelItem modelItem = ModelHelper.getModelItem(firstPathSegment, modelItemAccessors);
367        
368        if (modelItem instanceof ModelItemAccessor)
369        {
370            // Create the view item and add it to the current view item accessor
371            ModelViewItem viewItem = _createModelViewItemInstance(modelItem);
372            
373            // Add the view item to its parent
374            _addItemToViewItemAccessor(viewItemAccessor, viewItem, itemConfiguration, referenceView, override);
375            
376            if (firstIndexOfItemPathSeparator > -1)
377            {
378                // Only the first segment of the path has been processed, now recursively process the next ones
379                String subPath = modelItemAccessorPath.substring(firstIndexOfItemPathSeparator + 1);
380                return _createViewItemAccessor(itemConfiguration, subPath, (ViewItemAccessor) viewItem, List.of((ModelItemAccessor) modelItem), referenceView, override);
381            }
382            else
383            {
384                return (ViewItemAccessor) viewItem;
385            }
386        }
387        else
388        {
389            throw new ConfigurationException("Unable to get or create a view item accessor, the given path '" + modelItemAccessorPath + "' refers to a model item that is not an accessor");
390        }
391    }
392    
393    /**
394     * Retrieves the model item with the given name
395     * @param itemConfiguration configuration of the model view item
396     * @param modelItemName the model item name
397     * @param parents the accessors containing the model item
398     * @return the model item
399     * @throws ConfigurationException if the configuration is not valid.
400     */
401    protected ModelItem _getModelItem(ConfigurationAndPluginName itemConfiguration, String modelItemName, Collection<? extends ModelItemAccessor> parents) throws ConfigurationException
402    {
403        try
404        {
405            return ModelHelper.getModelItem(modelItemName, parents);
406        }
407        catch (IllegalArgumentException | UndefinedItemPathException e)
408        {
409            throw new ConfigurationException("The item '" + modelItemName + "' is not defined in model.", itemConfiguration.configuration(), e);
410        }
411    }
412    
413    /**
414     * Retrieves the model item reference from the given item configuration
415     * @param itemConfiguration the item configuration
416     * @return the model item reference
417     * @throws ConfigurationException if an error occurs while parsing the model item reference
418     */
419    protected String _getModelItemReference(Configuration itemConfiguration) throws ConfigurationException
420    {
421        return itemConfiguration.getAttribute(ITEM_REFERENCE_ATTRIBUTE_NAME);
422    }
423    
424    /**
425     * Retrieves the child configuration to add items to the current accessor
426     * @param accessorConfiguration the accessor configuration
427     * @return the child configuration to add items to the current accessor
428     */
429    protected Configuration _getAddItemChildConfiguration(Configuration accessorConfiguration)
430    {
431        return accessorConfiguration.getChild(ADD_ITEM_TAG_NAME, false);
432    }
433    
434    /**
435     * Parses the view element 
436     * @param itemConfiguration configuration of the view item
437     * @param definition definition of the element
438     * @param referenceView view that references the item
439     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
440     * @param context the context of the view parsing
441     * @return the view item
442     * @throws ConfigurationException if the configuration is not valid
443     */
444    protected ViewElement _parseViewElement(ConfigurationAndPluginName itemConfiguration, ElementDefinition definition, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
445    {
446        return (ViewElement) _createLeafModelViewItemInstance(definition);
447    }
448    
449    /**
450     * Creates an instance of {@link ModelViewItem} due to the given {@link ModelItem}
451     * @param modelItem the model item corresponding to the view item to create
452     * @return the created view item
453     */
454    @SuppressWarnings("unchecked")
455    protected ModelViewItem _createModelViewItemInstance(ModelItem modelItem)
456    {
457        ModelViewItem modelViewItem = ViewHelper.createModelViewItemInstance(modelItem);
458        modelViewItem.setDefinition(modelItem);
459        
460        return modelViewItem;
461    }
462    
463    /**
464     * Creates an instance of {@link ModelViewItem} due to the given {@link ModelItem}. The view item is a leaf of the view
465     * @param modelItem the model item corresponding to the view item to create
466     * @return the created view item
467     */
468    protected ModelViewItem _createLeafModelViewItemInstance(ModelItem modelItem)
469    {
470        return _createModelViewItemInstance(modelItem);
471    }
472    
473    /**
474     * Parses the item with the given configuration and add the item to the given view item accessor
475     * @param itemConfiguration the configuration of the item to parse
476     * @param viewItemAccessor the {@link ViewItemAccessor} that will access to the parsed items
477     * @param modelItemAccessor the {@link ModelItemAccessor} corresponding to the given view item accessor
478     * @param referenceView The view that references the item
479     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
480     * @param context the context of the view parsing
481     * @throws ConfigurationException if the configuration is not valid
482     */
483    protected void _parseViewItemAccessorChild(ConfigurationAndPluginName itemConfiguration, ViewItemAccessor viewItemAccessor, ModelItemAccessor modelItemAccessor, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
484    {
485        if (_isAddingItemConfiguration(itemConfiguration.configuration()))
486        {
487            _parseModelViewItem(itemConfiguration, viewItemAccessor, List.of(modelItemAccessor), referenceView, override, context);
488        }
489        else if (_isAddingGroupConfiguration(itemConfiguration.configuration()))
490        {
491            _parseSimpleViewItemGroup(itemConfiguration, ViewItemGroup.FIELDSET_ROLE, viewItemAccessor, List.of(modelItemAccessor), referenceView, override, context);
492        }
493    }
494    
495    /**
496     * Parses a simple view item group and add it to its parent
497     * @param itemConfiguration configuration of the simple view item group
498     * @param role the role of the view group
499     * @param parent the parent view item accessor
500     * @param modelItemAccessors the current parent model item accessors
501     * @param referenceView the reference view for includes
502     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
503     * @param context the context of the view parsing
504     * @throws ConfigurationException if the configuration is not valid.
505     */
506    protected void _parseSimpleViewItemGroup(ConfigurationAndPluginName itemConfiguration, String role, ViewItemAccessor parent, Collection<? extends ModelItemAccessor> modelItemAccessors, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
507    {
508        SimpleViewItemGroup group = new SimpleViewItemGroup();
509        group.setRole(itemConfiguration.configuration().getAttribute("role", role));
510        group.setName(itemConfiguration.configuration().getAttribute("name", null));
511        
512        group.setLabel(ItemParserHelper.parseI18nizableText(itemConfiguration, "label"));
513        group.setDescription(ItemParserHelper.parseI18nizableText(itemConfiguration, "description"));
514
515        for (Configuration childConfiguration : itemConfiguration.configuration().getChildren())
516        {
517            _parseSimpleViewItemGroupChild(new ConfigurationAndPluginName(childConfiguration, itemConfiguration.pluginName(), itemConfiguration.defaultI18nCatalog()), group, modelItemAccessors, referenceView, override, context);
518        }
519        
520        // Add the group to its parent
521        _addItemToViewItemAccessor(parent, group, itemConfiguration.configuration(), referenceView, override);
522    }
523    
524    /**
525     * Parses the item with the given configuration and add the item to the given group
526     * @param itemConfiguration configuration of the group's child
527     * @param group the simple view item group
528     * @param modelItemAccessors the current parent model item accessors
529     * @param referenceView the reference view for includes
530     * @param override <code>true</code> if the configuration is an override, <code>false</code> otherwise
531     * @param context the context of the view parsing
532     * @throws ConfigurationException if the configuration is not valid.
533     */
534    protected void _parseSimpleViewItemGroupChild(ConfigurationAndPluginName itemConfiguration, SimpleViewItemGroup group, Collection<? extends ModelItemAccessor> modelItemAccessors, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
535    {
536        if (_isAddingItemConfiguration(itemConfiguration.configuration()))
537        {
538            _parseModelViewItem(itemConfiguration, group, modelItemAccessors, referenceView, override, context);
539        }
540        else if (_isAddingGroupConfiguration(itemConfiguration.configuration()))
541        {
542            _parseSimpleViewItemGroup(itemConfiguration, ViewItemGroup.FIELDSET_ROLE, group, modelItemAccessors, referenceView, override, context);
543        }
544    }
545    
546    /**
547     * Add an item to a view or to an overridden view
548     * @param viewItemAccessor The view 
549     * @param viewItem The view item to add
550     * @param itemConfiguration The item's configurations
551     * @param referenceView the reference view for includes
552     * @param override <code>true</code> if the view is an override, <code>false</code> otherwise
553     * @throws ConfigurationException If an error occurs
554     */
555    protected void _addItemToViewItemAccessor(ViewItemAccessor viewItemAccessor, ViewItem viewItem, Configuration itemConfiguration, View referenceView, boolean override) throws ConfigurationException 
556    {
557        // Check already existing view items
558        if (getLogger().isWarnEnabled() && viewItem instanceof ModelViewItem modelViewItem && (viewItemAccessor.hasModelViewItem(modelViewItem) || referenceView.hasModelViewItem(modelViewItem)))
559        {
560            String itemPath = modelViewItem.getDefinition().getPath();
561            getLogger().warn("The item '" + itemPath + "' is already referenced by the view '" + referenceView.getName() + "'.");
562        }
563        
564        if (viewItemAccessor instanceof View view && override)
565        {
566            _addItemToOverriddenView(view, viewItem, itemConfiguration);
567        }
568        else
569        {
570            viewItemAccessor.addViewItem(viewItem);
571        }
572    }
573    
574    /**
575     * Add the viewItem to the view or in a location inside the view (in a group or before/after another view item)
576     * @param view The view in which to insert the new Item
577     * @param viewItem The viewItem to insert
578     * @param itemConfiguration The configuration of the viewItem
579     * @throws ConfigurationException If an error occurs
580     */
581    protected void _addItemToOverriddenView(View view, ViewItem viewItem, Configuration itemConfiguration) throws ConfigurationException 
582    {
583        String group = itemConfiguration.getAttribute("group", null);
584        ViewItemAccessor viewItemAccessor = view;
585        
586        if (group != null)
587        {
588            try
589            {
590                viewItemAccessor = ViewHelper.getSimpleViewItemGroup(view, group);
591            }
592            catch (IllegalArgumentException e)
593            {
594                throw new ConfigurationException("The path to the requested group where to add the view item " + viewItem.getName() + " in the initial view " + view.getName() + " is empty.", itemConfiguration, e);
595            }
596            catch (UndefinedItemPathException e)
597            {
598                getLogger().warn("The group requested " + group + " does not exist in the initial view " + view.getName() + ". The item '" + viewItem.getName() + "' will be inserted at the end of the view", e);
599            }
600        }
601        
602        _insertItemInViewItemAccessor(viewItemAccessor, viewItem, itemConfiguration);
603    }
604    
605    /**
606     * Insert an item in the given {@link ViewItemAccessor}
607     * @param viewItemAccessor The view item accessor in which to insert the new Item
608     * @param viewItem The viewItem to insert
609     * @param itemConfiguration The configuration of the viewItem
610     * @throws ConfigurationException If an error occurs
611     */
612    protected void _insertItemInViewItemAccessor(ViewItemAccessor viewItemAccessor, ViewItem viewItem, Configuration itemConfiguration) throws ConfigurationException
613    {
614        String before = itemConfiguration.getAttribute("order-before", null);
615        String after = itemConfiguration.getAttribute("order-after", null);
616        
617        // An attribute cannot be before an attribute and after at the same time
618        if (after != null && before != null)
619        {
620            throw new ConfigurationException("The item " + viewItem.getName() + " cannot be added both after and before attributes", itemConfiguration);
621        }
622        
623        if (after != null || before != null)
624        {
625            InsertMode insertMode = after != null ? InsertMode.AFTER : InsertMode.BEFORE;
626            String insertAfterOrBefore = after != null ? after : before;
627
628            try
629            {
630                ViewHelper.insertItemAfterOrBefore(viewItemAccessor, viewItem, insertAfterOrBefore, insertMode);
631            }
632            catch (IllegalArgumentException e)
633            {
634                throw new ConfigurationException("Unable to insert view item " + viewItem.getName() + " " + insertMode + " the specified view item. The name is empty or is a path.", itemConfiguration, e);
635            }
636            catch (UndefinedItemPathException e)
637            {
638                if (getLogger().isWarnEnabled())
639                {
640                    String viewItemAccessorName = viewItemAccessor instanceof View view
641                            ? view.getName() 
642                            : viewItemAccessor instanceof ViewItem vI
643                                ? vI.getName()
644                                : viewItemAccessor.toString();
645                    getLogger().warn("Unable to insert view item " + viewItem.getName() + " " + insertMode + " the view item named " + insertAfterOrBefore + ". No view item has been found with this name. This item will be inserted at the end of the view item accessor '" + viewItemAccessorName + "'.", e);
646                }
647                
648                // Add the item at the end of the view item accessor
649                viewItemAccessor.addViewItem(viewItem);
650            }
651        }
652        else
653        {
654            viewItemAccessor.addViewItem(viewItem);
655        }
656    }
657    
658    /**
659     * Removes a view item from the view
660     * @param view The view from which we want to remove an item
661     * @param itemConfiguration The configuration of the view item to remove
662     * @throws ConfigurationException If there is an error whil parsing the item configuration
663     */
664    protected void _removeViewItemFromView(View view, Configuration itemConfiguration) throws ConfigurationException
665    {
666        String viewItemPath = itemConfiguration.getAttribute(ITEM_REFERENCE_ATTRIBUTE_NAME);
667
668        try
669        {
670            ViewItem viewItem = ViewHelper.getViewItem(view, viewItemPath);
671            ViewItemAccessor parent = viewItem.getParent();
672            parent.removeViewItem(viewItem);
673        }
674        catch (Exception e)
675        {
676            // Just log the warning, do not throw exceptions
677            getLogger().warn("[View Item removal] Unable to remove " + viewItemPath + " from " + view.getName(), e);
678        } 
679    }
680}