001/* 002 * Copyright 2013 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.contenttype; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.avalon.framework.configuration.Configurable; 028import org.apache.avalon.framework.configuration.Configuration; 029import org.apache.avalon.framework.configuration.ConfigurationException; 030import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.commons.lang3.StringUtils; 035import org.apache.excalibur.source.Source; 036import org.apache.excalibur.source.SourceResolver; 037import org.xml.sax.SAXException; 038 039import org.ametys.core.ui.ClientSideElement.ScriptFile; 040import org.ametys.plugins.core.ui.util.ConfigurationHelper; 041import org.ametys.plugins.explorer.dublincore.DublinCoreMetadataProvider; 042import org.ametys.runtime.i18n.I18nizableText; 043import org.ametys.runtime.plugin.component.AbstractLogEnabled; 044import org.ametys.runtime.plugin.component.PluginAware; 045 046/** 047 * This abstract class represents a content type descriptor 048 */ 049public abstract class AbstractContentTypeDescriptor extends AbstractLogEnabled implements ContentTypeDescriptor, Configurable, PluginAware, Serviceable 050{ 051 /** Plugin name. */ 052 protected String _pluginName; 053 054 /** Content type id. */ 055 protected String _id; 056 /** Label. */ 057 protected I18nizableText _label; 058 /** Description. */ 059 protected I18nizableText _description; 060 /** Default title. */ 061 protected I18nizableText _defaultTitle; 062 /** Category. */ 063 protected I18nizableText _category; 064 /** Glyph icon */ 065 protected String _iconGlyph; 066 /** Icon decorator */ 067 protected String _iconDecorator; 068 /** Small icon URI 16x16. */ 069 protected String _smallIcon; 070 /** Medium icon URI 32x32. */ 071 protected String _mediumIcon; 072 /** Large icon URI 48x48. */ 073 protected String _largeIcon; 074 075 /** The CSS files */ 076 protected List<ScriptFile> _cssFiles; 077 078 /** This content-type's supertype ids (can be empty). */ 079 protected String[] _superTypeIds; 080 /** 081 * All metadata sets. 082 * @deprecated Use {@link DefaultContentType#_views} instead 083 */ 084 @Deprecated 085 protected Map<String, MetadataSet> _allMetadataSets = new LinkedHashMap<>(); 086 /** 087 * Non-internal metadata sets. 088 * @deprecated Use {@link DefaultContentType#_views} instead 089 */ 090 @Deprecated 091 protected Map<String, MetadataSet> _metadataSets = new LinkedHashMap<>(); 092 093 /** The content type extension point. */ 094 protected ContentTypeExtensionPoint _cTypeEP; 095 /** The content types helper */ 096 protected ContentTypesHelper _contentTypesHelper; 097 /** The content types parser helper */ 098 protected ContentTypesParserHelper _contentTypesParserHelper; 099 /** The source resolver */ 100 protected SourceResolver _srcResolver; 101 /** The DublinCore metadata provider */ 102 protected DublinCoreMetadataProvider _dcProvider; 103 104 @Override 105 public void service(ServiceManager smanager) throws ServiceException 106 { 107 _contentTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 108 _contentTypesParserHelper = (ContentTypesParserHelper) smanager.lookup(ContentTypesParserHelper.ROLE); 109 _srcResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 110 _dcProvider = (DublinCoreMetadataProvider) smanager.lookup(DublinCoreMetadataProvider.ROLE); 111 _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 112 } 113 114 @Override 115 public void setPluginInfo(String pluginName, String featureName, String id) 116 { 117 _pluginName = pluginName; 118 _id = id; 119 } 120 121 /** 122 * Get the root configuration 123 * @param configuration The configuration 124 * @return The main configuration 125 */ 126 protected abstract Configuration getRootConfiguration (Configuration configuration); 127 128 /** 129 * Get the overridden configuration 130 * @return the overridden configuration or null 131 * @throws ConfigurationException if an error occurred 132 * TODO NEWATTRIBUTEAPI_CONTENT: Remove this abstract method when {@link #_configureMetadataSets(Configuration)} is removed. This method will now only be used in {@link DefaultContentType} to configure attributes and views 133 */ 134 protected abstract Configuration getOverridenConfiguration() throws ConfigurationException; 135 136 @Override 137 public void configure(Configuration configuration) throws ConfigurationException 138 { 139 Configuration rootConfiguration = getRootConfiguration(configuration); 140 _configureSuperTypes(rootConfiguration); 141 142 _configureLabels(rootConfiguration); 143 _configureIcons(rootConfiguration); 144 145 // Metadata sets 146 _configureMetadataSets (rootConfiguration); 147 148 _configureCSSFiles(rootConfiguration); 149 } 150 151 /** 152 * Configures the super types 153 * @param mainConfig The main configuration 154 */ 155 protected void _configureSuperTypes (Configuration mainConfig) 156 { 157 String extendedCTypes = mainConfig.getAttribute("extends", null); 158 List<String> superTypeIds = new ArrayList<>(); 159 if (extendedCTypes != null) 160 { 161 String[] superIds = extendedCTypes.split(","); 162 for (String id : superIds) 163 { 164 superTypeIds.add(StringUtils.trim(id)); 165 } 166 } 167 168 _superTypeIds = superTypeIds.toArray(new String[superTypeIds.size()]); 169 } 170 171 /** 172 * Configures the labels 173 * @param mainConfig The main configuration 174 * @throws ConfigurationException if an error occurred 175 */ 176 protected void _configureLabels (Configuration mainConfig) throws ConfigurationException 177 { 178 _label = _contentTypesParserHelper.parseI18nizableText(this, mainConfig, "label"); 179 _description = _contentTypesParserHelper.parseI18nizableText(this, mainConfig, "description"); 180 _defaultTitle = _contentTypesParserHelper.parseI18nizableText(this, mainConfig, "default-title"); 181 _category = _contentTypesParserHelper.parseI18nizableText(this, mainConfig, "category"); 182 } 183 184 /** 185 * Configures the icons 186 * @param mainConfig The main configuration 187 * @throws ConfigurationException if an error occurred 188 */ 189 protected void _configureIcons (Configuration mainConfig) throws ConfigurationException 190 { 191 _iconGlyph = mainConfig.getChild("icons").getChild("glyph").getValue(null); 192 _iconDecorator = mainConfig.getChild("icons").getChild("decorator").getValue(null); 193 _smallIcon = _contentTypesParserHelper.parseIcon(this, mainConfig.getChild("icons"), "small"); 194 _mediumIcon = _contentTypesParserHelper.parseIcon(this, mainConfig.getChild("icons"), "medium"); 195 _largeIcon = _contentTypesParserHelper.parseIcon(this, mainConfig.getChild("icons"), "large"); 196 } 197 198 /** 199 * Configure metadata sets 200 * @param mainConfig The content type configuration 201 * @throws ConfigurationException if an error occurred 202 * @deprecated Use {@link DefaultContentType#_configureViews(Configuration)} instead 203 */ 204 @Deprecated 205 protected void _configureMetadataSets (Configuration mainConfig) throws ConfigurationException 206 { 207 // First, fill metadata-set from super types 208 _allMetadataSets.putAll(_contentTypesHelper.getMetadataSetsForView(_superTypeIds, new String[0], true)); 209 _metadataSets.putAll(_contentTypesHelper.getMetadataSetsForView(_superTypeIds, new String[0], false)); 210 211 Map<String, Configuration> metadataSetViewConfs = new LinkedHashMap<>(); 212 Map<String, Configuration> metadataSetEditConfs = new LinkedHashMap<>(); 213 214 _getApplicableMetadataSets(mainConfig, metadataSetViewConfs, metadataSetEditConfs, false); 215 216 Configuration overriddenConfig = getOverridenConfiguration(); 217 if (overriddenConfig != null) 218 { 219 _getApplicableMetadataSets(overriddenConfig, metadataSetViewConfs, metadataSetEditConfs, true); 220 } 221 222 // Then parse own metadata sets 223 _parseMetadataSets(metadataSetViewConfs, _metadataSets, _allMetadataSets, _superTypeIds); 224 _parseMetadataSets(metadataSetEditConfs, _metadataSets, _allMetadataSets, _superTypeIds); 225 } 226 227 /** 228 * Compute the applicable metadata-sets from their configurations. 229 * @param config The content type configuration 230 * @param metadataSetViewConfs the view metadata-set configurations, indexed by name. 231 * @param metadataSetEditConfs the edition metadata-set configurations, indexed by name. 232 * @param allowOverride if true, encountering a metadata-set which has already been declared (based on its name) will replace it. Otherwise, an exception will be thrown. 233 * @throws ConfigurationException if the configuration is invalid 234 * @deprecated Use {@link DefaultContentType#_getApplicableViews(Configuration, String, boolean)} instead 235 */ 236 @Deprecated 237 protected void _getApplicableMetadataSets(Configuration config, Map<String, Configuration> metadataSetViewConfs, Map<String, Configuration> metadataSetEditConfs, boolean allowOverride) throws ConfigurationException 238 { 239 for (Configuration metadataSetConfig : config.getChildren("metadata-set")) 240 { 241 String name = metadataSetConfig.getAttribute("name"); 242 String type = metadataSetConfig.getAttribute("type"); 243 244 if (type.equals("view")) 245 { 246 if (!allowOverride && metadataSetViewConfs.containsKey(name)) 247 { 248 throw new ConfigurationException("The view metadata-set '" + name + "' is already defined.", metadataSetConfig); 249 } 250 metadataSetViewConfs.put(name, metadataSetConfig); 251 } 252 else if (type.equals("edition")) 253 { 254 if (!allowOverride && metadataSetEditConfs.containsKey(name)) 255 { 256 throw new ConfigurationException("The edition metadata-set '" + name + "' is already defined.", metadataSetConfig); 257 } 258 metadataSetEditConfs.put(name, metadataSetConfig); 259 } 260 else 261 { 262 throw new ConfigurationException("Invalid type '" + type + "' for metadata set '" + name + "'", metadataSetConfig); 263 } 264 } 265 } 266 267 /** 268 * Parse the metadata-set configurations 269 * @param metadataSetConfs the metadata-set configurations, indexed by name. 270 * @param metadataSets the Map of "public" {@link MetadataSet} objects to fill, indexed by name 271 * @param allMetadataSets the Map of {@link MetadataSet} objects to fill (including internal ones), indexed by name. 272 * @param superTypeIds the super content-types 273 * @throws ConfigurationException if the configuration is invalid 274 * @deprecated Use {@link DefaultContentType#_parseViews(java.util.Collection)} instead 275 */ 276 @Deprecated 277 protected void _parseMetadataSets(Map<String, Configuration> metadataSetConfs, Map<String, MetadataSet> metadataSets, Map<String, MetadataSet> allMetadataSets, String[] superTypeIds) throws ConfigurationException 278 { 279 for (Configuration metadataSetConfig : metadataSetConfs.values()) 280 { 281 String name = metadataSetConfig.getAttribute("name"); 282 String type = metadataSetConfig.getAttribute("type"); 283 boolean isInternal = metadataSetConfig.getAttributeAsBoolean("internal", false); 284 boolean isEdition = type.equals("edition"); 285 286 MetadataSet metadataSet = _parseMetadataSet(metadataSetConfig, name, isEdition, isInternal, superTypeIds); 287 288 allMetadataSets.put(name, metadataSet); 289 290 if (!isInternal) 291 { 292 metadataSets.put(name, metadataSet); 293 } 294 } 295 } 296 297 /** 298 * Parse a metadata-set configuration to create a {@link MetadataSet} object. 299 * @param metadataSetConfig the metadata set configuration to use. 300 * @param metadataSetName the metadata set name. 301 * @param isEdition the metadata set edition status. 302 * @param isInternal the metadata internal status. 303 * @param superTypeIds the super types. 304 * @return the metadata set 305 * @throws ConfigurationException if the configuration is not valid. 306 * @deprecated Use {@link ContentTypeViewParser#parseView(Configuration)} instead 307 */ 308 @Deprecated 309 protected MetadataSet _parseMetadataSet(Configuration metadataSetConfig, String metadataSetName, boolean isEdition, boolean isInternal, String[] superTypeIds) throws ConfigurationException 310 { 311 MetadataSet metadataSet = new MetadataSet(); 312 313 I18nizableText label = _contentTypesParserHelper.parseI18nizableText(this, metadataSetConfig, "label", metadataSetName); 314 I18nizableText description = _contentTypesParserHelper.parseI18nizableText(this, metadataSetConfig, "description"); 315 316 Configuration iconConf = metadataSetConfig.getChild("icons"); 317 318 String smallIcon = _contentTypesParserHelper.parseIcon(this, iconConf, "small", null); 319 String mediumIcon = _contentTypesParserHelper.parseIcon(this, iconConf, "medium", null); 320 String largeIcon = _contentTypesParserHelper.parseIcon(this, iconConf, "large", null); 321 322 String iconGlyph = _contentTypesParserHelper.parseIconGlyph(iconConf, metadataSetName, mediumIcon == null ? "ametysicon-column3" : null); 323 String iconDecorator = iconConf.getChild("decorator").getValue(null); 324 325 metadataSet.setName(metadataSetName); 326 metadataSet.setLabel(label); 327 metadataSet.setDescription(description); 328 metadataSet.setIconGlyph(iconGlyph); 329 metadataSet.setIconDecorator(iconDecorator); 330 metadataSet.setSmallIcon(smallIcon); 331 metadataSet.setMediumIcon(mediumIcon); 332 metadataSet.setLargeIcon(largeIcon); 333 metadataSet.setEdition(isEdition); 334 335 MetadataSet superMetadataSet = null; 336 if (superTypeIds.length > 0) 337 { 338 if (isEdition) 339 { 340 superMetadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, superTypeIds, new String[0]); 341 } 342 else 343 { 344 superMetadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, superTypeIds, new String[0]); 345 } 346 } 347 348 _fillMetadataSetElement(metadataSet, metadataSetConfig, metadataSet, isEdition, superMetadataSet); 349 350 return metadataSet; 351 } 352 353 /** 354 * Fill child elements for a metadata set element. 355 * @param rootMetadataSet The root metadata set 356 * @param metadataSetConfig the metadata set configuration to use. 357 * @param metadataSetElement the metadata set element to fill. 358 * @param isEdition true if it is the edition mode 359 * @param superMetadataSet the overridden metadata-set if applicable, null otherwise. 360 * @throws ConfigurationException if the configuration is not valid. 361 * @deprecated Use {@link ContentTypeViewParser} instead 362 */ 363 @Deprecated 364 protected void _fillMetadataSetElement(MetadataSet rootMetadataSet, Configuration metadataSetConfig, AbstractMetadataSetElement metadataSetElement, boolean isEdition, MetadataSet superMetadataSet) throws ConfigurationException 365 { 366 for (Configuration elementConfig : metadataSetConfig.getChildren()) 367 { 368 String elementConfigName = elementConfig.getName(); 369 370 if (elementConfigName.equals("metadata-ref")) 371 { 372 String metadataName = elementConfig.getAttribute("name"); 373 String metadataSetName = elementConfig.getAttribute("metadata-set", null); 374 375 MetadataDefinitionReference metadataDefRef = new MetadataDefinitionReference(metadataName, metadataSetName); 376 377 if ("metadata-set".equals(metadataSetConfig.getName()) && rootMetadataSet.hasMetadataDefinitionReference(metadataName)) 378 { 379 throw new ConfigurationException("The metadata '" + metadataName + "' is already referenced by a super metadata-set or by the metadata-set '" + rootMetadataSet.getName() + "' itself.", elementConfig); 380 } 381 metadataSetElement.addElement(metadataDefRef); 382 383 _fillMetadataSetElement(rootMetadataSet, elementConfig, metadataDefRef, isEdition, superMetadataSet); 384 } 385 else if (elementConfigName.equals("fieldset")) 386 { 387 Fieldset fieldset = new Fieldset(); 388 fieldset.setRole(elementConfig.getAttribute("role", "tab")); 389 fieldset.setLabel(_contentTypesParserHelper.parseI18nizableText(this, elementConfig, "label")); 390 391 _fillMetadataSetElement(rootMetadataSet, elementConfig, fieldset, isEdition, superMetadataSet); 392 393 metadataSetElement.addElement(fieldset); 394 } 395 else if (elementConfigName.equals("dublin-core")) 396 { 397 _fillMetadataSetDublinCore(metadataSetElement); 398 } 399 else if (elementConfigName.equals("include")) 400 { 401 String supertype = elementConfig.getAttribute("from-supertype"); 402 if ("true".equals(supertype)) 403 { 404 if (superMetadataSet == null) 405 { 406 throw new ConfigurationException("The metadata-set element includes the super metadata-set without its content type referencing a super-type.", elementConfig); 407 } 408 409 _contentTypesHelper.copyMetadataSetElementsIfNotExist(superMetadataSet, metadataSetElement); 410 } 411 else 412 { 413 ContentType superType = _cTypeEP.getExtension(supertype); 414 if (superType == null) 415 { 416 throw new ConfigurationException("The metadata-set element includes the super metadata-set of an unknown super-type '" + supertype + "'.", elementConfig); 417 } 418 419 MetadataSet metadataSetToInclude = isEdition ? superType.getMetadataSetForEdition(rootMetadataSet.getName()) : superType.getMetadataSetForView(rootMetadataSet.getName()); 420 _contentTypesHelper.copyMetadataSetElementsIfNotExist(metadataSetToInclude, metadataSetElement); 421 } 422 } 423 } 424 } 425 426 /** 427 * Parse the DublinCore metadata set. 428 * @param metadataSetElement the metadata set element to fill. 429 * @throws ConfigurationException if the configuration is not valid. 430 * @deprecated Use {@link ContentTypeViewParser} instead 431 */ 432 @Deprecated 433 protected void _fillMetadataSetDublinCore(AbstractMetadataSetElement metadataSetElement) throws ConfigurationException 434 { 435 MetadataDefinitionReference dcMetaDefRef = new MetadataDefinitionReference("dc"); 436 437 Source src = null; 438 439 try 440 { 441 src = _srcResolver.resolveURI("resource://org/ametys/cms/dublincore/dublincore.xml"); 442 443 if (src.exists()) 444 { 445 try (InputStream is = src.getInputStream()) 446 { 447 Configuration configuration = new DefaultConfigurationBuilder(true).build(is); 448 449 Configuration metadataSetConf = configuration.getChild("metadata-set"); 450 451 for (Configuration childConfiguration : metadataSetConf.getChildren("metadata-ref")) 452 { 453 dcMetaDefRef.addElement(new MetadataDefinitionReference(childConfiguration.getAttribute("name"))); 454 } 455 } 456 } 457 } 458 catch (IOException e) 459 { 460 throw new ConfigurationException("Unable to parse Dublin Core metadata set", e); 461 } 462 catch (SAXException e) 463 { 464 throw new ConfigurationException("Unable to parse Dublin Core metadata set", e); 465 } 466 finally 467 { 468 if (src != null) 469 { 470 _srcResolver.release(src); 471 } 472 } 473 474 metadataSetElement.addElement(dcMetaDefRef); 475 } 476 477 /** 478 * Configure the CSS to import 479 * @param configuration The imports configuration 480 * @throws ConfigurationException If an error occurs 481 */ 482 protected void _configureCSSFiles(Configuration configuration) throws ConfigurationException 483 { 484 _cssFiles = ConfigurationHelper.parsePluginResourceList(configuration.getChild("css"), getPluginName(), getLogger()); 485 } 486 487 @Override 488 public String getPluginName() 489 { 490 return _pluginName; 491 } 492 493 @Override 494 public String getId() 495 { 496 return _id; 497 } 498 499 @Override 500 public I18nizableText getLabel() 501 { 502 return _label; 503 } 504 505 @Override 506 public I18nizableText getDescription() 507 { 508 return _description; 509 } 510 511 @Override 512 public I18nizableText getCategory() 513 { 514 return _category; 515 } 516 517 @Override 518 public I18nizableText getDefaultTitle() 519 { 520 return _defaultTitle; 521 } 522 523 @Override 524 public String getIconGlyph() 525 { 526 return _iconGlyph; 527 } 528 529 @Override 530 public String getIconDecorator() 531 { 532 return _iconDecorator; 533 } 534 535 @Override 536 public String getSmallIcon() 537 { 538 return _smallIcon; 539 } 540 541 @Override 542 public String getMediumIcon() 543 { 544 return _mediumIcon; 545 } 546 547 @Override 548 public String getLargeIcon() 549 { 550 return _largeIcon; 551 } 552 553 @Override 554 public String[] getSupertypeIds() 555 { 556 return _superTypeIds; 557 } 558 559 public List<ScriptFile> getCSSFiles() 560 { 561 return _cssFiles; 562 } 563 564 public Set<String> getEditionMetadataSetNames(boolean includeInternal) 565 { 566 if (includeInternal) 567 { 568 return Collections.unmodifiableSet(_allMetadataSets.keySet()); 569 } 570 else 571 { 572 return Collections.unmodifiableSet(_metadataSets.keySet()); 573 } 574 } 575 576 public Set<String> getViewMetadataSetNames(boolean includeInternal) 577 { 578 if (includeInternal) 579 { 580 return Collections.unmodifiableSet(_allMetadataSets.keySet()); 581 } 582 else 583 { 584 return Collections.unmodifiableSet(_metadataSets.keySet()); 585 } 586 } 587 588 public MetadataSet getMetadataSetForView(String metadataSetName) 589 { 590 return _allMetadataSets.get(metadataSetName); 591 } 592 593 public MetadataSet getMetadataSetForEdition(String metadataSetName) 594 { 595 return _allMetadataSets.get(metadataSetName); 596 } 597} 598