001/*
002 *  Copyright 2025 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.odf.content.code;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.commons.lang.StringUtils;
030
031import org.ametys.cms.contenttype.ContentType;
032import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
033import org.ametys.cms.contenttype.ContentTypesHelper;
034import org.ametys.cms.repository.Content;
035import org.ametys.runtime.model.ElementDefinition;
036import org.ametys.runtime.model.ModelItem;
037import org.ametys.runtime.model.type.ModelItemTypeConstants;
038import org.ametys.runtime.plugin.PluginsManager;
039
040/**
041 * Implementation of a {@link DisplayCodeProvider} based on {@link Content} attributes.<br>
042 * Display code will be composed by attributes' values separated by '-' character.
043 * <br>
044 * Expected configuration is:<br>
045 *   <pre>
046 *   &lt;type id="CONTENT_TYPE_ID_1">
047 *       &lt;item ref="path/of/attribute1"/>
048 *       &lt;item ref="path/of/attribute2"/>
049 *   &lt;/type>
050 *   &lt;type id="CONTENT_TYPE_ID_2">
051 *       &lt;item ref="path/of/attribute1"/>
052 *       &lt;item ref="path/of/attribute2"/>
053 *   &lt;/type>
054 *   </pre>
055 */
056public class AttributeBasedDisplayCodeProvider extends AbstractStaticProgramItemDisplayCodeProvider implements Serviceable
057{
058    private static final String __ATTRIBUTES_SEPARATOR = "-";
059    
060    /** The service manager */
061    protected ServiceManager _smanager;
062    /** The attributes composing the code by content types */
063    protected Map<String, List<String>> _attributesByTypes = new HashMap<>();
064    
065    private ContentTypesHelper _contentTypeHelper;
066    private ContentTypeExtensionPoint _cTypeEP;
067    
068    public void service(ServiceManager smanager) throws ServiceException
069    {
070        _smanager = smanager;
071    }
072    
073    @Override
074    public void configure(Configuration configuration) throws ConfigurationException
075    {
076        super.configure(configuration);
077        
078        if (!PluginsManager.getInstance().isSafeMode() && isActive())
079        {
080            // Check and fill configuration only if not in safe mode (to keep component safe)
081            Configuration[] cTypesConf = configuration.getChildren("type");
082            for (Configuration typeConf : cTypesConf)
083            {
084                String cTypeId = typeConf.getAttribute("id");
085                ContentType cType = getContentTypeExtensionPoint().getExtension(cTypeId);
086                if (cType == null)
087                {
088                    throw new ConfigurationException("Unknown content type " + cTypeId, typeConf);
089                }
090                
091                List<String> attributePaths = new ArrayList<>();
092                
093                for (Configuration itemConf : typeConf.getChildren("item"))
094                {
095                    String dataPath = itemConf.getAttribute("ref");
096                    if (!cType.hasModelItem(dataPath))
097                    {
098                        throw new ConfigurationException("Attribute '" + dataPath + "' is not part of the model for content type " + cTypeId, typeConf);
099                    }
100                    
101                    ModelItem modelItem = cType.getModelItem(dataPath);
102                    if (modelItem instanceof ElementDefinition definition && ModelItemTypeConstants.STRING_TYPE_ID.equals(definition.getType().getId()) && !definition.isMultiple())
103                    {
104                        attributePaths.add(dataPath);
105                    }
106                    else
107                    {
108                        throw new ConfigurationException("Item '" + dataPath + " is not a valid definition for display code, only non-multiple string attribute are allowed", typeConf);
109                    }
110                }
111                
112                _attributesByTypes.put(cTypeId, attributePaths);
113            }
114        }
115    }
116    
117    /**
118     * Get the content type extension point
119     * @return the content type extension point
120     */
121    protected ContentTypeExtensionPoint getContentTypeExtensionPoint()
122    {
123        // Lazing loading to keep component safe
124        if (_cTypeEP == null)
125        {
126            try
127            {
128                _cTypeEP = (ContentTypeExtensionPoint) _smanager.lookup(ContentTypeExtensionPoint.ROLE);
129            }
130            catch (ServiceException e)
131            {
132                throw new RuntimeException("Unable to lookup after the content type extension point", e);
133            }
134        }
135        
136        return _cTypeEP;
137    }
138    
139    /**
140     * Get content types helper
141     * @return the content types helper
142     */
143    protected ContentTypesHelper getContentTypesHelper()
144    {
145        // Lazing loading to keep component safe
146        if (_contentTypeHelper == null)
147        {
148            try
149            {
150                _contentTypeHelper = (ContentTypesHelper) _smanager.lookup(ContentTypesHelper.ROLE);
151            }
152            catch (ServiceException e)
153            {
154                throw new RuntimeException("Unable to lookup after the content types helper", e);
155            }
156        }
157        
158        return _contentTypeHelper;
159    }
160    
161    public Optional<String> getDisplayCode(Content content)
162    {
163        List<String> values = new ArrayList<>();
164        
165        for (String cTypeId : _attributesByTypes.keySet())
166        {
167            if (getContentTypesHelper().isInstanceOf(content, cTypeId))
168            {
169                List<String> attributePaths = _attributesByTypes.get(cTypeId);
170                for (String attributePath : attributePaths)
171                {
172                    String value = content.getValue(attributePath, false, null);
173                    if (StringUtils.isNotEmpty(value))
174                    {
175                        values.add(value);
176                    }
177                }
178            }
179        }
180        
181        return values.isEmpty() ? Optional.empty() : Optional.of(String.join(__ATTRIBUTES_SEPARATOR, values));
182    }
183
184    public boolean supports(Content content)
185    {
186        for (String cTypeId : _attributesByTypes.keySet())
187        {
188            if (getContentTypesHelper().isInstanceOf(content, cTypeId))
189            {
190                return true;
191            }
192        }
193        return false;
194    }
195}