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 * <type id="CONTENT_TYPE_ID_1"> 047 * <item ref="path/of/attribute1"/> 048 * <item ref="path/of/attribute2"/> 049 * </type> 050 * <type id="CONTENT_TYPE_ID_2"> 051 * <item ref="path/of/attribute1"/> 052 * <item ref="path/of/attribute2"/> 053 * </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}