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