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.odf.cdmfr; 017 018import static org.ametys.cms.data.type.ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID; 019import static org.ametys.cms.data.type.ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID; 020import static org.ametys.cms.data.type.ModelItemTypeConstants.REFERENCE_ELEMENT_TYPE_ID; 021import static org.ametys.cms.data.type.ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID; 022import static org.ametys.plugins.repository.data.type.ModelItemTypeConstants.COMPOSITE_TYPE_ID; 023import static org.ametys.plugins.repository.data.type.ModelItemTypeConstants.REPEATER_TYPE_ID; 024import static org.ametys.runtime.model.type.ModelItemTypeConstants.BOOLEAN_TYPE_ID; 025import static org.ametys.runtime.model.type.ModelItemTypeConstants.DATE_TYPE_ID; 026import static org.ametys.runtime.model.type.ModelItemTypeConstants.DOUBLE_TYPE_ID; 027import static org.ametys.runtime.model.type.ModelItemTypeConstants.LONG_TYPE_ID; 028import static org.ametys.runtime.model.type.ModelItemTypeConstants.STRING_TYPE_ID; 029 030import java.time.LocalDate; 031import java.util.HashMap; 032import java.util.Map; 033import java.util.Set; 034 035import org.apache.avalon.framework.configuration.Configurable; 036import org.apache.avalon.framework.configuration.Configuration; 037import org.apache.avalon.framework.configuration.ConfigurationException; 038import org.apache.avalon.framework.service.ServiceException; 039import org.apache.avalon.framework.service.ServiceManager; 040import org.apache.avalon.framework.service.Serviceable; 041import org.apache.cocoon.xml.AttributesImpl; 042import org.apache.cocoon.xml.XMLUtils; 043import org.apache.excalibur.source.SourceResolver; 044import org.xml.sax.ContentHandler; 045import org.xml.sax.SAXException; 046 047import org.ametys.cms.contenttype.ContentTypesHelper; 048import org.ametys.cms.data.ContentValue; 049import org.ametys.cms.data.File; 050import org.ametys.cms.data.Reference; 051import org.ametys.cms.data.RichText; 052import org.ametys.cms.repository.Content; 053import org.ametys.odf.course.Course; 054import org.ametys.odf.enumeration.OdfReferenceTableHelper; 055import org.ametys.odf.orgunit.OrgUnit; 056import org.ametys.odf.person.Person; 057import org.ametys.odf.program.AbstractProgram; 058import org.ametys.odf.program.Container; 059import org.ametys.odf.program.Program; 060import org.ametys.odf.program.ProgramFactory; 061import org.ametys.odf.program.SubProgram; 062import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 063import org.ametys.plugins.repository.data.holder.group.ModelAwareComposite; 064import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater; 065import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry; 066import org.ametys.plugins.repository.model.RepositoryDataContext; 067import org.ametys.runtime.i18n.I18nizableText; 068import org.ametys.runtime.model.ElementDefinition; 069import org.ametys.runtime.model.Enumerator; 070import org.ametys.runtime.model.ModelItem; 071import org.ametys.runtime.model.ModelItemGroup; 072import org.ametys.runtime.model.View; 073import org.ametys.runtime.model.type.DataContext; 074import org.ametys.runtime.model.type.ModelItemType; 075 076/** 077 * Simple {@link CDMfrExtension} generating CDM-fr Ametys extension for each configured attribute.<br> 078 * Each attribute should be configured like:<br><br> 079 * <code><attribute name="attributeName" tag="tagName"></code><br><br> 080 * and will be output with the following syntax:<br><br> 081 * <code><ametys-cdm:tagName>value</ametys-cdm:tagName></code><br><br> 082 * The tag name is optional, defaulting to the attribute name.<br> 083 * It the attribute is of type "content", then the "cdm" view is also exported inside the attribute's XML tag if it exists. 084 */ 085public class GenericCDMfrExtension extends AbstractCDMfrExtension implements Configurable, Serviceable 086{ 087 private SourceResolver _sourceResolver; 088 private OdfReferenceTableHelper _refTableHelper; 089 private ContentTypesHelper _contentTypesHelper; 090 091 private Map<String, String> _abstractPrograms = new HashMap<>(); 092 private Map<String, String> _programs = new HashMap<>(); 093 private Map<String, String> _subPrograms = new HashMap<>(); 094 private Map<String, String> _containers = new HashMap<>(); 095 private Map<String, String> _courses = new HashMap<>(); 096 private Map<String, String> _orgUnits = new HashMap<>(); 097 private Map<String, String> _persons = new HashMap<>(); 098 099 public void service(ServiceManager manager) throws ServiceException 100 { 101 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 102 _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE); 103 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 104 } 105 106 public void configure(Configuration configuration) throws ConfigurationException 107 { 108 _abstractPrograms = _configure(configuration, "abstractProgram"); 109 _programs = _configure(configuration, "program"); 110 _subPrograms = _configure(configuration, "subProgram"); 111 _containers = _configure(configuration, "container"); 112 _courses = _configure(configuration, "course"); 113 _orgUnits = _configure(configuration, "orgUnit"); 114 _persons = _configure(configuration, "person"); 115 } 116 117 private Map<String, String> _configure(Configuration configuration, String attributeSet) throws ConfigurationException 118 { 119 Map<String, String> attributeMap = new HashMap<>(); 120 121 for (Configuration conf : configuration.getChild(attributeSet, true).getChildren("attribute")) 122 { 123 String name = conf.getAttribute("name"); 124 String tag = conf.getAttribute("tag", name); 125 126 attributeMap.put(name, tag); 127 } 128 129 return attributeMap; 130 } 131 132 public void abstractProgram2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program, Set<String> persons, Set<String> orgUnits) throws SAXException 133 { 134 _attributes2CDM(contentHandler, program, _abstractPrograms); 135 } 136 137 public void program2CDM(ContentHandler contentHandler, Program program, Set<String> persons, Set<String> orgUnits) throws SAXException 138 { 139 _attributes2CDM(contentHandler, program, _programs); 140 } 141 142 public void subProgram2CDM(ContentHandler contentHandler, SubProgram subProgram, Set<String> persons, Set<String> orgUnits) throws SAXException 143 { 144 _attributes2CDM(contentHandler, subProgram, _subPrograms); 145 } 146 147 public void course2CDM(ContentHandler contentHandler, Course course, Set<String> persons, Set<String> orgUnits) throws SAXException 148 { 149 _attributes2CDM(contentHandler, course, _courses); 150 } 151 152 public void orgunit2CDM(ContentHandler contentHandler, OrgUnit orgunit) throws SAXException 153 { 154 _attributes2CDM(contentHandler, orgunit, _orgUnits); 155 } 156 157 public void person2CDM(ContentHandler contentHandler, Person person) throws SAXException 158 { 159 _attributes2CDM(contentHandler, person, _persons); 160 } 161 162 public void container2CDM(ContentHandler contentHandler, Container container, Set<String> persons, Set<String> orgUnits) throws SAXException 163 { 164 _attributes2CDM(contentHandler, container, _containers); 165 } 166 167 private void _attributes2CDM(ContentHandler contentHandler, Content content, Map<String, String> attributes) throws SAXException 168 { 169 for (Map.Entry<String, String> entry : attributes.entrySet()) 170 { 171 _attribute2CDM(contentHandler, content, content, null, entry.getKey(), entry.getValue()); 172 } 173 } 174 175 private void _attribute2CDM(ContentHandler contentHandler, ModelAwareDataHolder dataHolder, Content initialContent, String path, String attributeName, String tagName) throws SAXException 176 { 177 Object value = dataHolder.getValue(attributeName, false, null); 178 179 if (value == null) 180 { 181 return; 182 } 183 184 String dataPath = path == null ? attributeName : path + ModelItem.ITEM_PATH_SEPARATOR + attributeName; 185 186 ModelItem modelItem = dataHolder.getDefinition(attributeName); 187 ModelItemType type = modelItem.getType(); 188 String typeId = type.getId(); 189 190 if (modelItem instanceof ElementDefinition definition) 191 { 192 Object[] values = definition.isMultiple() ? (Object[]) value : new Object[] {value}; 193 194 switch (typeId) 195 { 196 case BOOLEAN_TYPE_ID: 197 case STRING_TYPE_ID: 198 case DOUBLE_TYPE_ID: 199 case LONG_TYPE_ID: 200 _simple2CDM(contentHandler, values, tagName, definition); 201 break; 202 case REFERENCE_ELEMENT_TYPE_ID: 203 _reference2CDM(contentHandler, values, tagName); 204 break; 205 case DATE_TYPE_ID: 206 _date2CDM(contentHandler, values, tagName); 207 break; 208 case RICH_TEXT_ELEMENT_TYPE_ID: 209 _richText2CDM(contentHandler, values, tagName, initialContent, dataPath); 210 break; 211 case CONTENT_ELEMENT_TYPE_ID: 212 _content2CDM(contentHandler, values, tagName, initialContent); 213 break; 214 case FILE_ELEMENT_TYPE_ID: 215 _file2CDM(contentHandler, values, tagName, initialContent, dataPath); 216 break; 217 default: 218 // Ignore it 219 break; 220 } 221 } 222 else if (typeId.equals(COMPOSITE_TYPE_ID)) 223 { 224 ModelAwareComposite composite = (ModelAwareComposite) value; 225 ModelItemGroup group = (ModelItemGroup) modelItem; 226 227 _composite2CDM(contentHandler, composite, group, initialContent, dataPath, tagName); 228 } 229 else if (typeId.equals(REPEATER_TYPE_ID)) 230 { 231 ModelAwareRepeater repeater = (ModelAwareRepeater) value; 232 ModelItemGroup group = (ModelItemGroup) modelItem; 233 234 _repeater2CDM(contentHandler, repeater, group, initialContent, dataPath, tagName); 235 } 236 } 237 238 private void _simple2CDM(ContentHandler contentHandler, Object[] values, String tagName, ElementDefinition definition) throws SAXException 239 { 240 Enumerator enumerator = definition.getEnumerator(); 241 242 for (Object v : values) 243 { 244 if (enumerator == null) 245 { 246 XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, v.toString()); 247 } 248 else 249 { 250 try 251 { 252 @SuppressWarnings("unchecked") 253 I18nizableText label = enumerator.getEntry(v); 254 255 AttributesImpl atts = new AttributesImpl(); 256 atts.addCDATAAttribute("value", v.toString()); 257 258 XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, atts); 259 label.toSAX(contentHandler); 260 XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName); 261 } 262 catch (Exception e) 263 { 264 throw new SAXException("Cannot retrieve enumerated label for value " + v + " for attribute " + definition.getName(), e); 265 } 266 } 267 } 268 } 269 270 private void _reference2CDM(ContentHandler contentHandler, Object[] values, String tagName) throws SAXException 271 { 272 for (Object v : values) 273 { 274 Reference ref = (Reference) v; 275 276 AttributesImpl attrs = new AttributesImpl(); 277 attrs.addCDATAAttribute("type", ref.getType()); 278 XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, attrs, ref.getValue()); 279 } 280 } 281 282 private void _date2CDM(ContentHandler contentHandler, Object[] values, String tagName) throws SAXException 283 { 284 for (Object v : values) 285 { 286 CDMHelper.date2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, (LocalDate) v); 287 } 288 } 289 290 private void _richText2CDM(ContentHandler contentHandler, Object[] values, String tagName, Content content, String dataPath) throws SAXException 291 { 292 for (Object v : values) 293 { 294 DataContext context = RepositoryDataContext.newInstance() 295 .withObject(content) 296 .withDataPath(dataPath); 297 298 CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, (RichText) v, context, _sourceResolver); 299 } 300 } 301 302 private void _content2CDM(ContentHandler contentHandler, Object[] values, String tagName, Content initialContent) throws SAXException 303 { 304 for (Object v : values) 305 { 306 Content content = ((ContentValue) v).getContentIfExists().orElse(null); 307 308 if (content != null) 309 { 310 if (_refTableHelper.isTableReferenceEntry(content)) 311 { 312 AttributesImpl attrs = new AttributesImpl(); 313 attrs.addCDATAAttribute("id", content.getId()); 314 attrs.addCDATAAttribute("code", _refTableHelper.getItemCDMfrValue(content.getId(), true)); 315 attrs.addCDATAAttribute("title", _refTableHelper.getItemLabel(content.getId(), initialContent.getLanguage())); 316 317 XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, attrs); 318 _contentView2CDM(contentHandler, content); 319 XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName); 320 } 321 else 322 { 323 AttributesImpl attrs = new AttributesImpl(); 324 attrs.addCDATAAttribute("id", content.getId()); 325 attrs.addCDATAAttribute("title", content.getTitle()); 326 327 XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, attrs); 328 _contentView2CDM(contentHandler, content); 329 XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName); 330 } 331 } 332 } 333 } 334 335 private void _contentView2CDM(ContentHandler contentHandler, Content content) throws SAXException 336 { 337 DataContext context = RepositoryDataContext.newInstance() 338 .withObject(content); 339 340 View view = _contentTypesHelper.getView("cdm", content); 341 342 if (view != null) 343 { 344 content.dataToSAX(contentHandler, view, context); 345 } 346 } 347 348 private void _file2CDM(ContentHandler contentHandler, Object[] values, String tagName, Content content, String dataPath) throws SAXException 349 { 350 for (Object v : values) 351 { 352 File file = (File) v; 353 XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, _getFileAbsoluteUrl(content, file, dataPath)); 354 } 355 } 356 357 private void _composite2CDM(ContentHandler contentHandler, ModelAwareComposite composite, ModelItemGroup group, Content initialContent, String dataPath, String tagName) throws SAXException 358 { 359 XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName); 360 361 for (ModelItem subAttribute : group.getChildren()) 362 { 363 _attribute2CDM(contentHandler, composite, initialContent, dataPath, subAttribute.getName(), subAttribute.getName()); 364 } 365 366 XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName); 367 } 368 369 private void _repeater2CDM(ContentHandler contentHandler, ModelAwareRepeater repeater, ModelItemGroup group, Content initialContent, String dataPath, String tagName) throws SAXException 370 { 371 372 for (ModelAwareRepeaterEntry entry : repeater.getEntries()) 373 { 374 XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName); 375 376 for (ModelItem subAttribute : group.getChildren()) 377 { 378 _attribute2CDM(contentHandler, entry, initialContent, dataPath + "[" + entry.getPosition() + "]", subAttribute.getName(), subAttribute.getName()); 379 } 380 381 XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName); 382 } 383 } 384}