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.cms.search.ui.model;
017
018import java.util.Collection;
019import java.util.List;
020import java.util.Optional;
021
022import org.apache.avalon.framework.configuration.ConfigurationException;
023import org.apache.avalon.framework.configuration.DefaultConfiguration;
024
025import org.ametys.cms.contenttype.AbstractContentTypeViewParser;
026import org.ametys.cms.contenttype.ContentType;
027import org.ametys.cms.repository.Content;
028import org.ametys.cms.search.model.SystemProperty;
029import org.ametys.cms.search.ui.model.impl.RepeaterSearchUIColumn;
030import org.ametys.plugins.repository.model.CompositeDefinition;
031import org.ametys.plugins.repository.model.RepeaterDefinition;
032import org.ametys.runtime.model.ElementDefinition;
033import org.ametys.runtime.model.ItemParserHelper.ConfigurationAndPluginName;
034import org.ametys.runtime.model.Model;
035import org.ametys.runtime.model.ModelItem;
036import org.ametys.runtime.model.ModelItemAccessor;
037import org.ametys.runtime.model.ModelViewItem;
038import org.ametys.runtime.model.ModelViewItemGroup;
039import org.ametys.runtime.model.View;
040import org.ametys.runtime.model.ViewItem;
041import org.ametys.runtime.model.ViewParserContext;
042
043/**
044 * Component that parses the columns of a {@link StaticSearchUIModel}
045 */
046public class StaticSearchUIModelColumnsParser extends AbstractContentTypeViewParser
047{
048    private static final String __VIEW_NAME = "StaticSearchUIModel-Columns";
049    
050    /** Content types containing the item available in the search model's columns */
051    protected Collection<ContentType> _contentTypes;
052
053    /**
054     * Creates a {@link StaticSearchUIModel}'s columns parser
055     * @param contentTypes The content types containing the item available in the search model's columns
056     */
057    public StaticSearchUIModelColumnsParser(Collection<ContentType> contentTypes)
058    {
059        _contentTypes = contentTypes;
060    }
061    
062    @Override
063    protected String _parseViewName(ConfigurationAndPluginName viewConfiguration) throws ConfigurationException
064    {
065        return __VIEW_NAME;
066    }
067
068    @Override
069    public View overrideView(ConfigurationAndPluginName viewConfiguration, View existingView, ViewParserContext context) throws ConfigurationException
070    {
071        throw new UnsupportedOperationException("Unable to override columns of a search model");
072    }
073    
074    @Override
075    protected void _fillViewGeneralInformation(ConfigurationAndPluginName viewConfiguration, View view, View existingView, Collection< ? extends Model> model) throws ConfigurationException
076    {
077        // Do nothing - there is no general information in search model's columns
078    }
079    
080    @Override
081    protected Collection<? extends Model> _getModel()
082    {
083        return _contentTypes;
084    }
085    
086    @Override
087    protected ModelItem _getModelItem(ConfigurationAndPluginName itemConfiguration, String modelItemName, Collection< ? extends ModelItemAccessor> parents) throws ConfigurationException
088    {
089        ModelItem modelItem = parents.isEmpty() && Content.ATTRIBUTE_TITLE.equals(modelItemName)
090                ? _contentTypesHelper.getTitleAttributeDefinition()
091                : super._getModelItem(itemConfiguration, modelItemName, parents);
092        
093        if (modelItem instanceof SystemProperty systemProperty && !systemProperty.isDisplayable())
094        {
095            throw new ConfigurationException("The property '" + systemProperty.getName() + "' is not displayable.");
096        }
097        
098        return modelItem;
099    }
100    
101    @Override
102    protected ModelViewItem createModelViewItemForAllItemsReference(ConfigurationAndPluginName itemConfiguration, ModelItem modelItem, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
103    {
104        if (modelItem instanceof CompositeDefinition compositeDefinition)
105        {
106            ModelViewItemGroup<CompositeDefinition> itemGroup = new ModelViewItemGroup<>();
107            itemGroup.setDefinition(compositeDefinition);
108            return itemGroup;
109        }
110        else
111        {
112            return super.createModelViewItemForAllItemsReference(itemConfiguration, modelItem, referenceView, override, context);
113        }
114    }
115    
116    @SuppressWarnings("unchecked")
117    @Override
118    protected ModelViewItem createModelViewItem(ConfigurationAndPluginName itemConfiguration, ModelItem modelItem, View referenceView, boolean override, ViewParserContext context) throws ConfigurationException
119    {
120        ModelViewItem<? extends ModelItem> modelViewItem = super.createModelViewItem(itemConfiguration, modelItem, referenceView, override, context);
121        
122        
123        if (modelViewItem instanceof SearchUIColumn searchColumn)
124        {
125            searchColumn.setName(itemConfiguration.configuration().getAttribute("name", null));
126            searchColumn.setWidth(itemConfiguration.configuration().getChild("width").getValueAsInteger(-1));
127            searchColumn.setHidden(itemConfiguration.configuration().getChild("hidden").getValueAsBoolean(false));
128            
129            searchColumn.setEditable(itemConfiguration.configuration().getChild("editable").getValueAsBoolean(true));
130            searchColumn.setSortable(itemConfiguration.configuration().getChild("sortable").getValueAsBoolean(true));
131            searchColumn.setAllowSortOnMultipleJoin(itemConfiguration.configuration().getChild("allow-sort-on-multiple-join").getValueAsBoolean(false));
132            searchColumn.setDefaultSorter(Optional.ofNullable(itemConfiguration.configuration().getChild("default-sorter").getValue(null)));
133            
134            searchColumn.setRenderer(Optional.ofNullable(itemConfiguration.configuration().getChild("renderer").getValue(null)));
135            searchColumn.setConverter(Optional.ofNullable(itemConfiguration.configuration().getChild("converter").getValue(null)));
136        }
137        
138        if (modelViewItem instanceof RepeaterSearchUIColumn repeaterColumn && modelViewItem.getDefinition() instanceof RepeaterDefinition repeaterDefinition
139                && !_getModelItemReference(itemConfiguration.configuration()).endsWith(ALL_ITEMS_REFERENCE)
140                && itemConfiguration.configuration().getChildren(ADD_ITEM_TAG_NAME).length == 0 && itemConfiguration.configuration().getChildren(ADD_GROUP_TAG_NAME).length == 0)
141        {
142            // Reference to a repeater with no child => add all model items in the view item
143            DefaultConfiguration fakeConfiguration = new DefaultConfiguration(ADD_ITEM_TAG_NAME);
144            fakeConfiguration.setAttribute(ITEM_REFERENCE_ATTRIBUTE_NAME, ALL_ITEMS_REFERENCE);
145            _parseModelViewItem(new ConfigurationAndPluginName(fakeConfiguration, itemConfiguration.pluginName(), itemConfiguration.defaultI18nCatalog()), repeaterColumn, List.of(repeaterDefinition), referenceView, override, context);
146        }
147            
148        return modelViewItem;
149    }
150    
151    @Override
152    protected ModelViewItem _createLeafModelViewItemInstance(ModelItem modelItem)
153    {
154        return SearchUIColumnHelper.createModelItemColumn(modelItem);
155    }
156    
157    @Override
158    protected ViewItem _createViewItemCopyInstanceForReferencedView(ViewItem viewItem)
159    {
160        return Optional.of(viewItem)
161                       .filter(ModelViewItem.class::isInstance)
162                       .map(ModelViewItem.class::cast)
163                       .map(ModelViewItem::getDefinition)
164                       // Keep only items that can be represented in columns
165                       .filter(modelViewItem -> modelViewItem instanceof RepeaterDefinition || modelViewItem instanceof ElementDefinition)
166                       .map(this::_createLeafModelViewItemInstance)
167                       .map(ViewItem.class::cast)
168                       .orElseGet(() -> super._createViewItemCopyInstanceForReferencedView(viewItem));
169    }
170}