001/*
002 *  Copyright 2014 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.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Comparator;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.LinkedHashMap;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Optional;
031import java.util.Set;
032import java.util.TreeSet;
033import java.util.function.Predicate;
034
035import org.apache.avalon.framework.activity.Disposable;
036import org.apache.avalon.framework.component.Component;
037import org.apache.avalon.framework.configuration.ConfigurationException;
038import org.apache.avalon.framework.logger.AbstractLogEnabled;
039import org.apache.avalon.framework.service.ServiceException;
040import org.apache.avalon.framework.service.ServiceManager;
041import org.apache.avalon.framework.service.Serviceable;
042import org.apache.avalon.framework.thread.ThreadSafe;
043import org.apache.commons.collections.CollectionUtils;
044import org.apache.commons.lang3.ArrayUtils;
045import org.apache.commons.lang3.StringUtils;
046import org.apache.commons.lang3.tuple.Pair;
047
048import org.ametys.cms.content.RootContentHelper;
049import org.ametys.cms.contenttype.indexing.IndexingField;
050import org.ametys.cms.contenttype.indexing.IndexingModel;
051import org.ametys.cms.repository.Content;
052import org.ametys.core.right.RightManager;
053import org.ametys.core.right.RightManager.RightResult;
054import org.ametys.core.ui.Callable;
055import org.ametys.core.user.CurrentUserProvider;
056import org.ametys.core.user.UserIdentity;
057import org.ametys.plugins.repository.AmetysRepositoryException;
058import org.ametys.runtime.i18n.I18nizableText;
059import org.ametys.runtime.model.ModelItem;
060import org.ametys.runtime.model.View;
061import org.ametys.runtime.model.ViewItem;
062
063/**
064 * Helper for manipulating {@link ContentType}s
065 * 
066 */
067public class ContentTypesHelper extends AbstractLogEnabled implements Component, Serviceable, ThreadSafe, Disposable
068{
069    /** The Avalon role */
070    public static final String ROLE = ContentTypesHelper.class.getName();
071
072    /** The content types extension point */
073    protected ContentTypeExtensionPoint _cTypeEP;
074    /** The current user provider */
075    protected CurrentUserProvider _userProvider;
076    /** The rights manager */
077    protected RightManager _rightManager;
078    /** Helper for root content */
079    protected RootContentHelper _rootContentHelper;
080    
081    private DynamicContentTypeDescriptorExtentionPoint _dynamicCTDescriptorEP;
082
083    /**
084     * Cache containing view metadata sets
085     * @deprecated use {@link #_viewCache} instead
086     */
087    @Deprecated
088    private Map<String, Map<String, MetadataSet>> _cacheForView = new HashMap<>();
089    
090    /**
091     * Cache containing edition metadata sets
092     * @deprecated use {@link #_viewCache} instead
093     */
094    @Deprecated
095    private Map<String, Map<String, MetadataSet>> _cacheForEdition = new HashMap<>();
096    
097    /**
098     * Cache containing content types' views
099     * The key is a cache identifier that is a concatenation of the identifiers of the concerned content types and mixins
100     * The value is a Map containing the views by their names
101     */
102    private Map<String, Map<String, View>> _viewCache = new HashMap<>();
103
104    private ServiceManager _smanager;
105
106    @Override
107    public void service(ServiceManager smanager) throws ServiceException
108    {
109        _smanager = smanager;
110        _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
111        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
112        _rootContentHelper = (RootContentHelper) smanager.lookup(RootContentHelper.ROLE);
113    }
114
115    private DynamicContentTypeDescriptorExtentionPoint _getDynamicContentTypeDescriptorExtentionPoint()
116    {
117        if (_dynamicCTDescriptorEP == null)
118        {
119            try
120            {
121                _dynamicCTDescriptorEP = (DynamicContentTypeDescriptorExtentionPoint) _smanager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE);
122            }
123            catch (ServiceException e)
124            {
125                throw new IllegalStateException(e);
126            }
127        }
128        return _dynamicCTDescriptorEP;
129    }
130    
131    @Override
132    public void dispose()
133    {
134        _cacheForView = new HashMap<>();
135        _cacheForEdition = new HashMap<>();
136        _viewCache = new HashMap<>();
137    }
138    
139    /**
140     * Lazy lookup of {@link ContentTypeExtensionPoint}
141     * @return the content type extension point
142     */
143    protected ContentTypeExtensionPoint _getContentTypeEP()
144    {
145        if (_cTypeEP == null)
146        {
147            try
148            {
149                _cTypeEP = (ContentTypeExtensionPoint) _smanager.lookup(ContentTypeExtensionPoint.ROLE);
150            }
151            catch (ServiceException e)
152            {
153                throw new RuntimeException("Unable to lookup ContentTypeExtensionPoint component", e);
154            }
155        }
156        return _cTypeEP;
157    }
158
159    /**
160     * Determines if a content is a instance of given content type id
161     * 
162     * @param content The content
163     * @param cTypeId The id of content type or mixin
164     * @return <code>true</code> if the content is an instance of content type
165     */
166    public boolean isInstanceOf(Content content, String cTypeId)
167    {
168        String[] types = content.getTypes();
169        if (ArrayUtils.contains(types, cTypeId))
170        {
171            return true;
172        }
173
174        String[] mixins = content.getMixinTypes();
175        if (ArrayUtils.contains(mixins, cTypeId))
176        {
177            return true;
178        }
179
180        return _containsContentType(ArrayUtils.addAll(types, mixins), cTypeId);
181    }
182
183    private boolean _containsContentType(String[] cTypesId, String cTypeId)
184    {
185        for (String id : cTypesId)
186        {
187            ContentType cType = _getContentTypeEP().getExtension(id);
188            if (cType != null)
189            {
190                if (ArrayUtils.contains(cType.getSupertypeIds(), cTypeId))
191                {
192                    return true;
193                }
194                else if (_containsContentType(cType.getSupertypeIds(), cTypeId))
195                {
196                    return true;
197                }
198            }
199        }
200        return false;
201    }
202
203    /**
204     * Get the id of the content type common ancestor
205     * 
206     * @param contentTypes The content types to compare
207     * @return The id of common ancestor or <code>null</code> if not found
208     */
209    public String getCommonAncestor(Collection<String> contentTypes)
210    {
211        if (contentTypes.isEmpty())
212        {
213            return null;
214        }
215
216        if (contentTypes.size() == 1)
217        {
218            return contentTypes.iterator().next();
219        }
220
221        List<Collection<String>> superTypesByCType = new ArrayList<>();
222        for (String id : contentTypes)
223        {
224            Set<String> superTypes = new HashSet<>();
225
226            superTypes.add(id);
227            superTypes.addAll(getAncestors(id));
228
229            superTypesByCType.add(superTypes);
230        }
231        
232        Iterator<Collection<String>> superTypesByCTypeIt = superTypesByCType.iterator();
233        
234        Set<String> commonCTypes = new HashSet<>(superTypesByCTypeIt.next());
235        while (superTypesByCTypeIt.hasNext() && !commonCTypes.isEmpty())
236        {
237            commonCTypes.retainAll(superTypesByCTypeIt.next());
238        }
239        
240        commonCTypes = removeAncestors(commonCTypes);
241        
242        if (commonCTypes.size() == 1)
243        {
244            return commonCTypes.iterator().next();
245        }
246
247        return null; // No common ancestor
248    }
249
250    /**
251     * Remove all content types in the set that are ancestors of other content types in the set.
252     * @param contentTypes a Set of content type IDs.
253     * @return a Set of content type IDs without ancestors.
254     */
255    protected Set<String> removeAncestors(Set<String> contentTypes)
256    {
257        Set<String> noAncestors = new HashSet<>(contentTypes);
258        
259        Iterator<String> it1 = contentTypes.iterator();
260        while (it1.hasNext())
261        {
262            ContentType cType1 = _getContentTypeEP().getExtension(it1.next());
263            
264            Iterator<String> it2 = contentTypes.iterator();
265            while (it2.hasNext())
266            {
267                String cType2 = it2.next();
268                String[] supertypeIds = cType1.getSupertypeIds();
269                
270                // CType2 is an ancestor of CType1: remove it.
271                if (ArrayUtils.contains(supertypeIds, cType2))
272                {
273                    noAncestors.remove(cType2);
274                }
275            }
276        }
277        
278        return noAncestors;
279    }
280
281    /**
282     * Get all ancestors for the given content type
283     * 
284     * @param contentTypeId The content type id to test
285     * @return A non-null set of all ancestors. Does not contains the contentTypeId itself.
286     * @throws IllegalArgumentException if the content type does not exist.
287     */
288    public Set<String> getAncestors(String contentTypeId)
289    {
290        Set<String> superTypes = new HashSet<>();
291
292        ContentType cType = _getContentTypeEP().getExtension(contentTypeId);
293        
294        if (cType == null)
295        {
296            throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'");
297        }
298        
299
300        String[] supertypeIds = cType.getSupertypeIds();
301
302        for (String superTypeId : supertypeIds)
303        {
304            superTypes.add(superTypeId);
305            superTypes.addAll(getAncestors(superTypeId));
306        }
307
308        return superTypes;
309
310    }
311    
312    /**
313     * Get super type's ids for the given content type
314     * The first entry contains super content types, the second one contains super mixin types 
315     * 
316     * @param contentTypeId The content type id to test
317     * @return An array of super type's ids.
318     * @throws IllegalArgumentException if the content type does not exist.
319     */
320    public Pair<String[], String[]> getSupertypeIds(String contentTypeId)
321    {
322        ContentType contentType = _getContentTypeEP().getExtension(contentTypeId);
323        if (contentType == null)
324        {
325            throw new IllegalArgumentException("Unable to get super types of unknown content type '" + contentTypeId + "'");
326        }
327
328        List<String> superMixins = new ArrayList<>();
329        List<String> superContentTypes = new ArrayList<>();
330        for (String supertypeId : contentType.getSupertypeIds())
331        {
332            ContentType supertype = _getContentTypeEP().getExtension(supertypeId);
333            if (supertype == null)
334            {
335                throw new IllegalArgumentException("Unable to get the unknown super type '" + supertypeId + "' for type '" + contentTypeId + "'");
336            }
337
338            if (supertype.isMixin())
339            {
340                superMixins.add(supertypeId);
341            }
342            else
343            {
344                superContentTypes.add(supertypeId);
345            }
346        }
347        
348        String[] superContentTypesArray = superContentTypes.toArray(new String[superContentTypes.size()]);
349        String[] superMixinsArray = superMixins.toArray(new String[superMixins.size()]);
350        return Pair.of(superContentTypesArray, superMixinsArray);
351    }
352
353    /**
354     * Get plugin name for the given content type
355     * 
356     * @param contentTypeId The content type id to test
357     * @return the plugin name
358     * @throws IllegalArgumentException if the content type does not exist.
359     */
360    public String getPluginName(String contentTypeId)
361    {
362        ContentType cType = _getContentTypeEP().getExtension(contentTypeId);
363        if (cType == null)
364        {
365            throw new IllegalArgumentException("Unable to get plugin name of unknown content type '" + contentTypeId + "'");
366        }
367
368        return cType.getPluginName();
369    }
370    
371    /**
372     * Builds the reverse hierarchies of ancestors of a content type
373     * @param contentTypeId The content type's id
374     * @return the reverse hierarchies with ancestors
375     */
376    public List<Set<String>> buildReverseHierarchies(String contentTypeId)
377    {
378        Set<String> hierarchy = new LinkedHashSet<>();
379        hierarchy.add(contentTypeId);
380        return _buildReverseHierarchies (contentTypeId, hierarchy);
381    }
382    
383    private List<Set<String>> _buildReverseHierarchies(String contentTypeId, Set<String> hierarchy)
384    {
385        List<Set<String>> hierarchies = new ArrayList<>();
386        
387        ContentType cType = _getContentTypeEP().getExtension(contentTypeId);
388        if (cType == null)
389        {
390            throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'");
391        }
392        
393        String[] supertypeIds = cType.getSupertypeIds();
394        if (supertypeIds.length > 0)
395        {
396            for (String superTypeId : supertypeIds)
397            {
398                Set<String> superHierarchy = new LinkedHashSet<>(hierarchy);
399                superHierarchy.add(superTypeId);
400                hierarchies.addAll(_buildReverseHierarchies(superTypeId, superHierarchy));
401            }
402        }
403        else
404        {
405            hierarchies.add(hierarchy);
406        }
407        
408        return hierarchies;
409    }
410    
411    /**
412     * Retrieves the root metadata names of a content.
413     * @param content The content.
414     * @return the metadata names.
415     */
416    public Set<String> getMetadataNames(Content content)
417    {
418        return getMetadataNames(content.getTypes(), content.getMixinTypes());
419    }
420    
421    /**
422     * Retrieves the metadata names resulting of the union of metadata
423     * names of given content types and mixins
424     * 
425     * @param cTypes The id of content types
426     * @param mixins The id of mixins
427     * @return the metadata names.
428     */
429    public Set<String> getMetadataNames(String[] cTypes, String[] mixins)
430    {
431        Set<String> metadataNames = new HashSet<>();
432
433        for (String id : cTypes)
434        {
435            ContentType cType = _getContentTypeEP().getExtension(id);
436            metadataNames.addAll(cType.getMetadataNames());
437        }
438
439        for (String id : mixins)
440        {
441            ContentType cType = _getContentTypeEP().getExtension(id);
442            metadataNames.addAll(cType.getMetadataNames());
443        }
444
445        return metadataNames;
446    }
447    
448    /**
449     * Get all views resulting of the concatenation of views of given content types and mixins.
450     * @param contentTypeIds the identifiers of the content types
451     * @param mixinIds the identifiers of the mixins
452     * @return The views
453     */
454    public Map<String, View> getViews(String[] contentTypeIds, String[] mixinIds)
455    {
456        return getViews(contentTypeIds, mixinIds, Collections.emptySet());
457    }
458    
459    /**
460     * Get all views resulting of the concatenation of views of given content types and mixins.
461     * @param contentTypeIds the identifiers of the content types
462     * @param mixinIds the identifiers of the mixins
463     * @param viewNamesToAvoid names of views that should not be managed
464     * @return The views
465     */
466    public Map<String, View> getViews(String[] contentTypeIds, String[] mixinIds, Set<String> viewNamesToAvoid)
467    {
468        Map<String, View> views = new HashMap<>();
469        
470        for (String contentTypeId : contentTypeIds)
471        {
472            ContentType contentType = _getContentTypeEP().getExtension(contentTypeId);
473            for (String viewName : contentType.getViewNames())
474            {
475                if (!viewNamesToAvoid.contains(viewName) && !views.containsKey(viewName))
476                {
477                    views.put(viewName, getView(viewName, contentTypeIds, mixinIds));
478                }
479            }
480        }
481        
482        return views;
483    }
484    
485    /**
486     * Get the view for view resulting of the concatenation of views of the given content types and mixins.
487     * @param viewName the name of the view to retrieve
488     * @param contentTypeIds the identifiers of the content types
489     * @param mixinIds the identifiers of the mixins
490     * @return The view.
491     */
492    public View getView(String viewName, String[] contentTypeIds, String[] mixinIds)
493    {
494        String cacheId = _getCacheIdentifier(contentTypeIds, mixinIds);
495
496        if (_viewCache.containsKey(cacheId) && _viewCache.get(cacheId).containsKey(viewName))
497        {
498            return _viewCache.get(cacheId).get(viewName);
499        }
500
501        View joinView = new View();
502        boolean foundOne = false;
503
504        for (String contentTypeId : contentTypeIds)
505        {
506            ContentType contentType = _getContentTypeEP().getExtension(contentTypeId);
507
508            View view = contentType.getView(viewName);
509            if (view != null)
510            {
511                foundOne = true;
512                joinView.includeView(view);
513
514                if (joinView.getName() == null)
515                {
516                    joinView.setName(viewName);
517                    joinView.setLabel(view.getLabel());
518                    joinView.setDescription(view.getDescription());
519                    joinView.setIconGlyph(view.getIconGlyph());
520                    joinView.setIconDecorator(view.getIconDecorator());
521                    joinView.setSmallIcon(view.getSmallIcon());
522                    joinView.setMediumIcon(view.getMediumIcon());
523                    joinView.setLargeIcon(view.getLargeIcon());
524                }
525            }
526        }
527
528        for (String id : mixinIds)
529        {
530            ContentType mixin = _getContentTypeEP().getExtension(id);
531
532            View view = mixin.getView(viewName);
533            if (view != null)
534            {
535                foundOne = true;
536                joinView.includeView(view);
537            }
538        }
539
540        if (!foundOne)
541        {
542            return null;
543        }
544        
545        if (!_viewCache.containsKey(cacheId))
546        {
547            _viewCache.put(cacheId, new HashMap<String, View>());
548        }
549        _viewCache.get(cacheId).put(viewName, joinView);
550
551        return joinView;
552    }
553
554    /**
555     * Get all metadata sets for view resulting of the concatenation of metadata
556     * sets of given content types and mixins.
557     * 
558     * @param cTypes The id of content types
559     * @param mixins The id of mixins
560     * @param includeInternal <code>true</code> True to include internal
561     *            metadata sets
562     * @return The metadata sets
563     * @deprecated Use {@link #getViews(String[], String[])} instead
564     */
565    @Deprecated
566    public Map<String, MetadataSet> getMetadataSetsForView(String[] cTypes, String[] mixins, boolean includeInternal)
567    {
568        Map<String, MetadataSet> metadataSets = new HashMap<>();
569
570        Set<String> viewMetadataSetNames = new HashSet<>();
571        for (String id : cTypes)
572        {
573            ContentType cType = _getContentTypeEP().getExtension(id);
574            for (String name : cType.getViewMetadataSetNames(includeInternal))
575            {
576                if (!viewMetadataSetNames.contains(name))
577                {
578                    viewMetadataSetNames.add(name);
579                }
580            }
581        }
582
583        for (String viewMetadataSetName : viewMetadataSetNames)
584        {
585            metadataSets.put(viewMetadataSetName, getMetadataSetForView(viewMetadataSetName, cTypes, mixins));
586        }
587
588        return metadataSets;
589    }
590
591    /**
592     * Get all metadata sets for edition resulting of the concatenation of
593     * metadata sets of given content types and mixins.
594     * 
595     * @param cTypes The id of content types
596     * @param mixins The id of mixins
597     * @param includeInternal <code>true</code> True to include internal
598     *            metadata sets
599     * @return The metadata sets
600     * @deprecated Use {@link #getViews(String[], String[])} instead
601     */
602    @Deprecated
603    public Map<String, MetadataSet> getMetadataSetsForEdition(String[] cTypes, String[] mixins, boolean includeInternal)
604    {
605        Map<String, MetadataSet> metadataSets = new HashMap<>();
606
607        Set<String> editionMetadataSetNames = new HashSet<>();
608        for (String id : cTypes)
609        {
610            ContentType cType = _getContentTypeEP().getExtension(id);
611            for (String name : cType.getEditionMetadataSetNames(includeInternal))
612            {
613                if (!editionMetadataSetNames.contains(name))
614                {
615                    editionMetadataSetNames.add(name);
616                }
617            }
618        }
619
620        for (String editionMetadataSetName : editionMetadataSetNames)
621        {
622            metadataSets.put(editionMetadataSetName, getMetadataSetForEdition(editionMetadataSetName, cTypes, mixins));
623        }
624
625        return metadataSets;
626    }
627
628    /**
629     * Get the metadata set for view resulting of the concatenation of metadata
630     * sets of given content types and mixins.
631     * 
632     * @param metadataSetName the name of metadata set to retrieve
633     * @param cTypes The id of content types
634     * @param mixins The id of mixins
635     * @return The list of metadata set names.
636     * @deprecated Use {@link #getView(String, String[], String[])} instead
637     */
638    @Deprecated
639    public MetadataSet getMetadataSetForView(String metadataSetName, String[] cTypes, String[] mixins)
640    {
641        String cacheId = _getCacheIdentifier(cTypes, mixins);
642
643        if (_cacheForView.containsKey(cacheId) && _cacheForView.get(cacheId).containsKey(metadataSetName))
644        {
645            return _cacheForView.get(cacheId).get(metadataSetName);
646        }
647
648        MetadataSet joinMetadataSet = new MetadataSet();
649        boolean foundOne = false;
650
651        for (String id : cTypes)
652        {
653            ContentType cType = _getContentTypeEP().getExtension(id);
654
655            MetadataSet metadataSet = cType.getMetadataSetForView(metadataSetName);
656            if (metadataSet != null)
657            {
658                foundOne = true;
659                copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet);
660
661                if (joinMetadataSet.getName() == null)
662                {
663                    joinMetadataSet.setName(metadataSetName);
664                    joinMetadataSet.setLabel(metadataSet.getLabel());
665                    joinMetadataSet.setDescription(metadataSet.getDescription());
666                    joinMetadataSet.setIconGlyph(metadataSet.getIconGlyph());
667                    joinMetadataSet.setIconDecorator(metadataSet.getIconDecorator());
668                    joinMetadataSet.setSmallIcon(metadataSet.getSmallIcon());
669                    joinMetadataSet.setMediumIcon(metadataSet.getMediumIcon());
670                    joinMetadataSet.setLargeIcon(metadataSet.getLargeIcon());
671                    joinMetadataSet.setEdition(false);
672                }
673            }
674        }
675
676        for (String id : mixins)
677        {
678            ContentType mixin = _getContentTypeEP().getExtension(id);
679
680            MetadataSet metadataSet = mixin.getMetadataSetForView(metadataSetName);
681            if (metadataSet != null)
682            {
683                foundOne = true;
684                copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet);
685            }
686        }
687
688        if (!foundOne)
689        {
690            return null;
691        }
692        
693        if (!_cacheForView.containsKey(cacheId))
694        {
695            _cacheForView.put(cacheId, new HashMap<String, MetadataSet>());
696        }
697        _cacheForView.get(cacheId).put(metadataSetName, joinMetadataSet);
698
699        return joinMetadataSet;
700    }
701
702    /**
703     * Get the metadata set for edition resulting of the concatenation of
704     * metadata sets of given content types and mixins.
705     * 
706     * @param metadataSetName the name of metadata set to retrieve
707     * @param cTypes The id of content types
708     * @param mixins The id of mixins
709     * @return The metadata set
710     * @deprecated Use {@link #getView(String, String[], String[])} instead
711     */
712    @Deprecated
713    public MetadataSet getMetadataSetForEdition(String metadataSetName, String[] cTypes, String[] mixins)
714    {
715        String cacheId = _getCacheIdentifier(cTypes, mixins);
716
717        if (_cacheForEdition.containsKey(cacheId) && _cacheForEdition.get(cacheId).containsKey(metadataSetName))
718        {
719            return _cacheForEdition.get(cacheId).get(metadataSetName);
720        }
721
722        MetadataSet joinMetadataSet = new MetadataSet();
723        boolean foundOne = false;
724
725        for (String id : cTypes)
726        {
727            ContentType cType = _getContentTypeEP().getExtension(id);
728
729            MetadataSet metadataSet = cType.getMetadataSetForEdition(metadataSetName);
730            if (metadataSet != null)
731            {
732                foundOne = true;
733                copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet);
734
735                if (joinMetadataSet.getName() == null)
736                {
737                    joinMetadataSet.setName(metadataSetName);
738                    joinMetadataSet.setLabel(metadataSet.getLabel());
739                    joinMetadataSet.setDescription(metadataSet.getDescription());
740                    joinMetadataSet.setIconGlyph(metadataSet.getIconGlyph());
741                    joinMetadataSet.setIconDecorator(metadataSet.getIconDecorator());
742                    joinMetadataSet.setSmallIcon(metadataSet.getSmallIcon());
743                    joinMetadataSet.setMediumIcon(metadataSet.getMediumIcon());
744                    joinMetadataSet.setLargeIcon(metadataSet.getLargeIcon());
745                    joinMetadataSet.setEdition(true);
746                }
747            }
748        }
749
750        for (String id : mixins)
751        {
752            ContentType mixin = _getContentTypeEP().getExtension(id);
753
754            MetadataSet metadataSet = mixin.getMetadataSetForEdition(metadataSetName);
755            if (metadataSet != null)
756            {
757                foundOne = true;
758                copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet);
759            }
760        }
761
762        if (!foundOne)
763        {
764            return null;
765        }
766        
767        if (!_cacheForEdition.containsKey(cacheId))
768        {
769            _cacheForEdition.put(cacheId, new HashMap<String, MetadataSet>());
770        }
771        _cacheForEdition.get(cacheId).put(metadataSetName, joinMetadataSet);
772
773        return joinMetadataSet;
774    }
775    
776    /**
777     * Get the metadata sets of a content type
778     * @param cTypeId the content type id
779     * @param edition Set to true to get edition metadata set. False otherwise.
780     * @param includeInternal Set to true to include internal metadata sets.
781     * @return the metadata sets info
782     * @deprecated Use {@link View} API instead
783     */
784    @Deprecated
785    @Callable
786    public List<Map<String, Object>> getMetadataSetsInfo(String cTypeId, boolean edition, boolean includeInternal)
787    {
788        List<Map<String, Object>> metadataSets = new ArrayList<>();
789        
790        ContentType cType = _getContentTypeEP().getExtension(cTypeId);
791        
792        Set<String> metadataSetNames = edition ? cType.getEditionMetadataSetNames(includeInternal) : cType.getViewMetadataSetNames(includeInternal);
793        for (String metadataSetName : metadataSetNames)
794        {
795            MetadataSet metadataSet = edition ? cType.getMetadataSetForEdition(metadataSetName) : cType.getMetadataSetForView(metadataSetName);
796            
797            Map<String, Object> viewInfos = new HashMap<>();
798            viewInfos.put("name", metadataSetName);
799            viewInfos.put("label", metadataSet.getLabel());
800            viewInfos.put("description", metadataSet.getDescription());
801            metadataSets.add(viewInfos);
802        }
803        
804        return metadataSets;
805    }
806    
807    /**
808     * Get the indexing model resulting of the concatenation of indexing models of given content types and mixins.
809     * @param content The content.
810     * @return The indexing model
811     */
812    public IndexingModel getIndexingModel(Content content)
813    {
814        return getIndexingModel(content.getTypes(), content.getMixinTypes());
815    }
816
817    /**
818     * Get the indexing model resulting of the concatenation of indexing models of given content types and mixins.
819     * @param cTypes The id of content types
820     * @param mixins The id of mixins
821     * @return The indexing model
822     */
823    public IndexingModel getIndexingModel(String[] cTypes, String[] mixins)
824    {
825        IndexingModel joinIndexingModel = new IndexingModel();
826        
827        for (String id : cTypes)
828        {
829            ContentType cType = _getContentTypeEP().getExtension(id);
830
831            if (cType != null)
832            {
833                IndexingModel indexingModel = cType.getIndexingModel();
834                
835                for (IndexingField field : indexingModel.getFields())
836                {
837                    joinIndexingModel.addIndexingField(field);
838                }
839            }
840            else
841            {
842                if (getLogger().isWarnEnabled())
843                {
844                    getLogger().warn(String.format("Trying to get indexing model for an unknown content type : '%s'.", id));
845                }
846            }
847        }
848        
849        for (String id : mixins)
850        {
851            ContentType mixin = _getContentTypeEP().getExtension(id);
852            
853            if (mixin != null)
854            {
855                IndexingModel indexingModel = mixin.getIndexingModel();
856                
857                for (IndexingField field : indexingModel.getFields())
858                {
859                    joinIndexingModel.addIndexingField(field);
860                }
861            }
862            else
863            {
864                if (getLogger().isWarnEnabled())
865                {
866                    getLogger().warn(String.format("Trying to get indexing model for an unknown mixin type : '%s'.", id));
867                }
868            }
869        }
870        
871        return joinIndexingModel;
872    }
873    
874    
875    /**
876     * Copy the elements of metadata set into a metadata set of destination,
877     * only if the elements are not already presents
878     * 
879     * @param src The metadata set to copy
880     * @param dest The metadata of destination
881     * @deprecated Use {@link View#includeView(View)} instead
882     */
883    @Deprecated
884    public void copyMetadataSetElementsIfNotExist(AbstractMetadataSetElement src, AbstractMetadataSetElement dest)
885    {
886        _copyMetadataSetElementsIfNotExist(src, dest, null);
887    }
888
889    /**
890     * Copy the elements of the source to the destination if they do not exist in the reference
891     * @param src the source
892     * @param dest the destination
893     * @param reference the reference
894     * @deprecated Use {@link View#includeView(View)} instead
895     */
896    @Deprecated
897    private void _copyMetadataSetElementsIfNotExist(AbstractMetadataSetElement src, AbstractMetadataSetElement dest, AbstractMetadataSetElement reference)
898    {
899        AbstractMetadataSetElement root = reference == null ? dest : reference;
900
901        for (AbstractMetadataSetElement elmt : src.getElements())
902        {
903            if (elmt instanceof MetadataDefinitionReference)
904            {
905                String metadataName = ((MetadataDefinitionReference) elmt).getMetadataName();
906                if (!root.hasMetadataDefinitionReference(metadataName))
907                {
908                    dest.addElement(elmt);
909                }
910            }
911            else if (elmt instanceof Fieldset)
912            {
913                Fieldset fieldset = new Fieldset();
914                fieldset.setLabel(((Fieldset) elmt).getLabel());
915                fieldset.setRole(((Fieldset) elmt).getRole());
916
917                _copyMetadataSetElementsIfNotExist(elmt, fieldset, root);
918
919                if (fieldset.getElements().size() > 0)
920                {
921                    dest.addElement(fieldset);
922                }
923            }
924        }
925    }
926    
927    /**
928     * Retrieve the definition of a given content metadata value path.
929     * @param metadataValuePath the metadata value path: it is separated by slashes and repeaters are valued: eg: "mycomposite/myrepeater[3]/mymetadata".
930     * @param content the content.
931     * @return the metadata definition or <code>null</code> if not found
932     * @deprecated Use {@link AttributeDefinition} API instead
933     */
934    @Deprecated
935    public MetadataDefinition getMetadataDefinitionByMetadataValuePath(String metadataValuePath, Content content)
936    {
937        return getMetadataDefinitionByMetadataValuePath(metadataValuePath, content.getTypes(), content.getMixinTypes());
938    }
939
940    /**
941     * Retrieve the definition of a given content metadata value path.
942     * @param metadataValuePath the metadata value path: it is separated by slashes and repeaters are valued: eg: "mycomposite.myrepeater[3].mymetadata".
943     * @param cTypes The id of content types
944     * @param mixins The id of mixins
945     * @return the metadata definition or <code>null</code> if not found
946     * @deprecated Use {@link AttributeDefinition} API instead
947     */
948    @Deprecated
949    public MetadataDefinition getMetadataDefinitionByMetadataValuePath(String metadataValuePath, String[] cTypes, String[] mixins)
950    {
951        for (String id : cTypes)
952        {
953            ContentType cType = _getContentTypeEP().getExtension(id);
954
955            MetadataDefinition metadataDef = getMetadataDefinitionByMetadataValuePath(metadataValuePath, cType);
956            if (metadataDef != null)
957            {
958                return metadataDef;
959            }
960        }
961
962        for (String id : mixins)
963        {
964            ContentType cType = _getContentTypeEP().getExtension(id);
965
966            MetadataDefinition metadataDef = getMetadataDefinitionByMetadataValuePath(metadataValuePath, cType);
967            if (metadataDef != null)
968            {
969                return metadataDef;
970            }
971        }
972
973        return null;
974    }
975    
976    /**
977     * Retrieve the definition of a given content metadata value path.
978     * @param metadataValuePath the metadata value path: it is separated by slashes and repeaters are valued: eg: "mycomposite/myrepeater[3]/mymetadata".
979     * @param metadataDefHolder The metadata def holder (such as the content type)
980     * @return the metadata definition or <code>null</code> if not found
981     * @deprecated Use {@link AttributeDefinition} API instead
982     */
983    @Deprecated
984    private MetadataDefinition getMetadataDefinitionByMetadataValuePath(String metadataValuePath, MetadataDefinitionHolder metadataDefHolder)
985    {
986        String metadataName = StringUtils.substringBefore(metadataValuePath, ContentConstants.METADATA_PATH_SEPARATOR);
987        String subMetadataPath = StringUtils.substringAfter(metadataValuePath, ContentConstants.METADATA_PATH_SEPARATOR);
988        // Ignore the repeater entry if any (we're only browsing the model).
989        metadataName = StringUtils.substringBefore(metadataName, "[");
990        
991        MetadataDefinition metadataDefinition = metadataDefHolder.getMetadataDefinition(metadataName);
992        if (metadataDefinition != null && StringUtils.isNotBlank(subMetadataPath))
993        {
994            if (metadataDefinition.getType() == MetadataType.COMPOSITE)
995            {
996                return getMetadataDefinitionByMetadataValuePath(subMetadataPath, metadataDefinition);
997            }
998            else
999            {
1000                return null;
1001            }
1002        }
1003        
1004        return metadataDefinition;
1005    }
1006    
1007    /**
1008     * Get a flat map of MetadataDefinition corresponding to a metadataset of a content
1009     * @param metadataSet The metadataset to get
1010     * @return The flat map of metadatapath and MetadataDefinition
1011     * @deprecated Use {@link View} API instead
1012     */
1013    @Deprecated
1014    public Set<String> getMetadataPaths(MetadataSet metadataSet)
1015    {
1016        Set<String> results = new HashSet<>();
1017
1018        for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements())
1019        {
1020            results.addAll(getMetadataPaths(subMetadataSetElement, ""));
1021        }
1022        
1023        return results;
1024    }
1025    
1026    /**
1027     * Get a flat map of metadatadefinition corresponding to a metadataset of a content
1028     * @param metadataSet The metadataset to get
1029     * @param prefix The metadata path of the parent
1030     * @return The flat map of metadatapath and metadatadefinition
1031     * @deprecated Use {@link ViewItem} API instead
1032     */
1033    @Deprecated
1034    protected Set<String> getMetadataPaths(AbstractMetadataSetElement metadataSet, String prefix)
1035    {
1036        Set<String> results = new HashSet<>();
1037        
1038        MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) metadataSet;
1039        
1040        String metadataName = metadataDefRef.getMetadataName();
1041        
1042        String metadataPath = prefix + metadataName;
1043        results.add(metadataPath);
1044        
1045        for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements())
1046        {
1047            results.addAll(getMetadataPaths(subMetadataSetElement, metadataPath + ContentConstants.METADATA_PATH_SEPARATOR));
1048        }
1049        
1050        return results;
1051    }    
1052    
1053    /** 
1054     * Retrieve the metadata definition represented by the given path.
1055     * The path can represent a metadata on another content.
1056     * @param metadataPath the metadata path separated by '/'
1057     * @param content The content.
1058     * @return the metadata definition or null.
1059     * @deprecated Use {@link Content#getDefinition(String)} API instead
1060     */
1061    @Deprecated
1062    public MetadataDefinition getMetadataDefinition(String metadataPath, Content content)
1063    {
1064        List<MetadataDefinition> metadataDefinitionsByPath = getMetadataDefinitionPath(metadataPath, content);
1065        return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null;
1066    }
1067
1068    /**
1069     * Get a flat map of MetadataDefinition corresponding to a set of metadata paths
1070     * @param metadataPaths A set of metadata path to analyse
1071     * @param content The content associated
1072     * @return The flat map of metadatapath and MetadataDefinition
1073     * @deprecated Use {@link AttributeDefinition} API instead
1074     */
1075    @Deprecated
1076    public Map<String, MetadataDefinition> getMetadataDefinitions(Set<String> metadataPaths, Content content)
1077    {
1078        Map<String, MetadataDefinition> results = new HashMap<>();
1079        
1080        for (String metadataPath : metadataPaths)
1081        {
1082            results.put(metadataPath, getMetadataDefinition(metadataPath, content));
1083        }
1084        
1085        return results;
1086    }
1087    
1088    /**
1089     * Get a flat map of MetadataDefinition corresponding to a metadataSet
1090     * @param metadataSet A metadataSet to analyse
1091     * @param content The content associated
1092     * @return The flat map of metadatapath and MetadataDefinition
1093     * @deprecated Use {@link AttributeDefinition} API instead
1094     */
1095    @Deprecated
1096    public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataSet metadataSet, Content content)
1097    {
1098        return getMetadataDefinitions(getMetadataPaths(metadataSet), content);
1099    }
1100    
1101    /** 
1102     * Retrieve the list of successive metadata definitions represented by the given path.
1103     * The path can represent a metadata on another content.
1104     * @param metadataPath the metadata path separated by '/'
1105     * @param content The content.
1106     * @return the list of metadata definitions, one by path element.
1107     * @deprecated Use {@link AttributeDefinition} API instead
1108     */
1109    @Deprecated
1110    public List<MetadataDefinition> getMetadataDefinitionPath(String metadataPath, Content content)
1111    {
1112        return getMetadataDefinitionPath(metadataPath, content.getTypes(), content.getMixinTypes());
1113    }
1114    
1115    /**
1116     * Get a flat map of MetadataDefinition path corresponding to a set of metadata paths
1117     * @param metadataPaths A set of metadata path to analyse
1118     * @param content The content associated
1119     * @return The flat map of metadatapath and corresponding list of MetadataDefinition path
1120     * @deprecated Use {@link AttributeDefinition} API instead
1121     */
1122    @Deprecated
1123    public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(Set<String> metadataPaths, Content content)
1124    {
1125        Map<String, List<MetadataDefinition>> results = new HashMap<>();
1126        
1127        for (String metadataPath : metadataPaths)
1128        {
1129            results.put(metadataPath, getMetadataDefinitionPath(metadataPath, content));
1130        }
1131        
1132        return results;
1133    }
1134    
1135    /**
1136     * Get a flat map of MetadataDefinition path corresponding to a metadataset
1137     * @param metadataSet A metadataset to analyse
1138     * @param content The content associated
1139     * @return The flat map of metadatapath and corresponding list of MetadataDefinition path
1140     * @deprecated Use {@link AttributeDefinition} API instead
1141     */
1142    @Deprecated
1143    public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(MetadataSet metadataSet, Content content)
1144    {
1145        return getMetadataDefinitionsPaths(getMetadataPaths(metadataSet), content);
1146    }
1147    
1148    /** 
1149     * Retrieve the metadata definition represented by the given path.
1150     * The path can represent a metadata on another content.
1151     * @param metadataPath the metadata path separated by '/'
1152     * @param cTypes The content types.
1153     * @param mixins The content mixins types.
1154     * @return the metadata definition or null.
1155     * @deprecated Use {@link AttributeDefinition} API instead
1156     */
1157    @Deprecated
1158    public MetadataDefinition getMetadataDefinition(String metadataPath, String[] cTypes, String[] mixins)
1159    {
1160        List<MetadataDefinition> metadataDefinitionsByPath = getMetadataDefinitionPath(metadataPath, cTypes, mixins);
1161        return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null;
1162    }
1163
1164    /** 
1165     * Get a flat map of MetadataDefinition corresponding to a set of metadata paths
1166     * @param metadataPaths A set of metadata path to analyse
1167     * @param cTypes The content types.
1168     * @param mixins The content mixins types.
1169     * @return The flat map of metadatapath and MetadataDefinition
1170     * @deprecated Use {@link AttributeDefinition} API instead
1171     */
1172    @Deprecated
1173    public Map<String, MetadataDefinition> getMetadataDefinitions(Set<String> metadataPaths, String[] cTypes, String[] mixins)
1174    {
1175        Map<String, MetadataDefinition> results = new HashMap<>();
1176        
1177        for (String metadataPath : metadataPaths)
1178        {
1179            results.put(metadataPath, getMetadataDefinition(metadataPath, cTypes, mixins));
1180        }
1181        
1182        return results;
1183    }
1184    
1185    /** 
1186     * Get a flat map of MetadataDefinition corresponding to a metadataset
1187     * @param metadataSet A metadataset to analyse
1188     * @param cTypes The content types.
1189     * @param mixins The content mixins types.
1190     * @return The flat map of metadatapath and MetadataDefinition
1191     * @deprecated Use {@link AttributeDefinition} API instead
1192     */
1193    @Deprecated
1194    public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataSet metadataSet, String[] cTypes, String[] mixins)
1195    {
1196        return getMetadataDefinitions(getMetadataPaths(metadataSet), cTypes, mixins);
1197    }
1198    
1199    /**
1200     * Get a flat map of MetadataDefinition corresponding to the required predicate (boolean-valued function)
1201     *  For example, to get all metadata definitions of type CONTENT, we can do:
1202     *      getMetadataDefinitions(contentType, m -&gt; m.getType() == MetadataType.CONTENT)
1203     *      
1204     * @param metaDefHolder The metadata definition holder
1205     * @param predicate The predicate (ex. m -&gt; m.getType() == MetadataType.CONTENT)
1206     * @return  The flat map of metadata's path and their definition
1207     * @deprecated Use {@link AttributeDefinition} API instead
1208     */
1209    @Deprecated
1210    public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataDefinitionHolder metaDefHolder, Predicate<MetadataDefinition> predicate)
1211    {
1212        Map<String, MetadataDefinition> metaDefs = new HashMap<>();
1213        
1214        Set<String> metadataNames = metaDefHolder.getMetadataNames();
1215        for (String metadataName : metadataNames)
1216        {
1217            MetadataDefinition metadataDef = metaDefHolder.getMetadataDefinition(metadataName);
1218            if (predicate.test(metadataDef))
1219            {
1220                metaDefs.put(metadataDef.getId(), metadataDef);
1221            }
1222            
1223            if (metadataDef.getType() == MetadataType.COMPOSITE || metadataDef instanceof RepeaterDefinition)
1224            {
1225                metaDefs.putAll(getMetadataDefinitions(metadataDef, predicate));
1226            }
1227        }
1228        
1229        return metaDefs;
1230    }
1231    
1232    /** 
1233     * Retrieve the list of successive metadata definitions represented by the given path.
1234     * The path can represent a metadata on another content.
1235     * @param metadataPath the metadata path separated by '/'
1236     * @param cTypes The id of content types
1237     * @param mixins The id of mixins
1238     * @return the metadata definition or <code>null</code> if not found
1239     * @deprecated Use {@link AttributeDefinition} API instead
1240     */
1241    @Deprecated
1242    public List<MetadataDefinition> getMetadataDefinitionPath(String metadataPath, String[] cTypes, String[] mixins)
1243    {
1244        String[] allContentTypes = ArrayUtils.addAll(cTypes, mixins);
1245        
1246        for (String cTypeId : allContentTypes)
1247        {
1248            ContentType cType = _getContentTypeEP().getExtension(cTypeId);
1249            if (cType != null)
1250            {
1251                List<MetadataDefinition> metaDefs = getMetadataDefinitionPath(metadataPath, cType);
1252                if (!metaDefs.isEmpty())
1253                { 
1254                    return metaDefs;
1255                }
1256            }
1257            else
1258            {
1259                if (getLogger().isWarnEnabled())
1260                {
1261                    getLogger().warn("Unknown content type identifier : " + cTypeId);
1262                }
1263            }
1264        }
1265        
1266        return Collections.emptyList();
1267    }
1268    
1269    /**
1270     * Get a flat map of MetadataDefinition path corresponding to a set of metadata paths
1271     * @param metadataPaths A set of metadata path to analyse
1272     * @param cTypes The id of content types
1273     * @param mixins The id of mixins
1274     * @return The flat map of metadatapath and corresponding list of MetadataDefinition path
1275     * @deprecated Use {@link AttributeDefinition} API instead
1276     */
1277    @Deprecated
1278    public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(Set<String> metadataPaths, String[] cTypes, String[] mixins)
1279    {
1280        Map<String, List<MetadataDefinition>> results = new HashMap<>();
1281        
1282        for (String metadataPath : metadataPaths)
1283        {
1284            results.put(metadataPath, getMetadataDefinitionPath(metadataPath, cTypes, mixins));
1285        }
1286        
1287        return results;
1288    }
1289    
1290    /**
1291     * Get a flat map of MetadataDefinition path corresponding to a metadataset
1292     * @param metadataSet A metadataset to analyse
1293     * @param cTypes The id of content types
1294     * @param mixins The id of mixins
1295     * @return The flat map of metadatapath and corresponding list of MetadataDefinition path
1296     * @deprecated Use {@link AttributeDefinition} API instead
1297     */
1298    @Deprecated
1299    public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(MetadataSet metadataSet, String[] cTypes, String[] mixins)
1300    {
1301        return getMetadataDefinitionsPaths(getMetadataPaths(metadataSet), cTypes, mixins);
1302    }
1303    
1304    /** 
1305     * Retrieves a metadata definition from a path. The metadata can be defined in a referenced or sub content.
1306     * @param metadataPath the metadata path separated by '/'
1307     * @param initialContentType The initial content type to start the search
1308     * @return the metadata definition or <code>null</code> if not found 
1309     * @deprecated Use {@link ContentType#getModelItem(String)} instead
1310     */
1311    @Deprecated
1312    public MetadataDefinition getMetadataDefinition(String metadataPath, ContentType initialContentType)
1313    {
1314        List<MetadataDefinition> metadataDefinitionsByPath = getMetadataDefinitionPath(metadataPath, initialContentType);
1315        return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null;
1316    }
1317    
1318    /**
1319     * Get a flat map of MetadataDefinition corresponding to a set of metadata paths
1320     * @param metadataPaths A set of metadata path to analyse
1321     * @param initialContentType The initial content type to start the search
1322     * @return The flat map of metadatapath and MetadataDefinition
1323     * @deprecated Use {@link AttributeDefinition} API instead
1324     */
1325    @Deprecated
1326    public Map<String, MetadataDefinition> getMetadataDefinitions(Set<String> metadataPaths, ContentType initialContentType)
1327    {
1328        Map<String, MetadataDefinition> results = new HashMap<>();
1329        
1330        for (String metadataPath : metadataPaths)
1331        {
1332            results.put(metadataPath, getMetadataDefinition(metadataPath, initialContentType));
1333        }
1334        
1335        return results;
1336    }
1337
1338    /**
1339     * Get a flat map of MetadataDefinition corresponding to a metadataset
1340     * @param metadataSet A metadataset to analyse
1341     * @param initialContentType The initial content type to start the search
1342     * @return The flat map of metadatapath and MetadataDefinition
1343     * @deprecated Use {@link AttributeDefinition} API instead
1344     */
1345    @Deprecated
1346    public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataSet metadataSet, ContentType initialContentType)
1347    {
1348        return getMetadataDefinitions(getMetadataPaths(metadataSet), initialContentType);
1349    }
1350    
1351    /** 
1352     * Retrieve the list of successive metadata definitions represented by the given path.
1353     * The path can represent a metadata on another content.
1354     * @param initialContentType The initial content type to start the search
1355     * @param metadataPath the metadata path separated by '/'
1356     * @return the list of metadata definitions, one by path element.
1357     * @deprecated Use {@link ContentType#getModelItem(String)} API instead
1358     */
1359    @Deprecated
1360    public List<MetadataDefinition> getMetadataDefinitionPath(String metadataPath, ContentType initialContentType)
1361    {
1362        List<MetadataDefinition> definitions = new ArrayList<>();
1363        
1364        String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR);
1365        
1366        if (pathSegments.length > 0)
1367        {
1368            MetadataDefinition metadataDef = initialContentType.getMetadataDefinition(pathSegments[0]);
1369            
1370            if (metadataDef != null)
1371            {
1372                definitions.add(metadataDef);
1373            }
1374            else
1375            {
1376                return Collections.emptyList();
1377            }
1378            
1379            for (int i = 1; i < pathSegments.length; i++)
1380            {
1381                if (metadataDef.getType() == MetadataType.CONTENT || metadataDef.getType() == MetadataType.SUB_CONTENT)
1382                {
1383                    String refCTypeId = metadataDef.getContentType();
1384                    if (refCTypeId != null && _getContentTypeEP().hasExtension(refCTypeId))
1385                    {
1386                        ContentType refCType = _getContentTypeEP().getExtension(refCTypeId);
1387                        
1388                        List<MetadataDefinition> followingDefs = getMetadataDefinitionPath(StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, i, pathSegments.length), refCType);
1389                        if (CollectionUtils.isEmpty(followingDefs))
1390                        {
1391                            return Collections.emptyList();
1392                        }
1393                        definitions.addAll(followingDefs);
1394                        
1395                        return definitions;
1396                    }
1397                    else if ("title".equals(pathSegments[i]))
1398                    {
1399                        // No specific content type: allow only title.
1400                        definitions.add(getTitleMetadataDefinition());
1401                        return definitions;
1402                    }
1403                    else
1404                    {
1405                        return Collections.emptyList();
1406                    }
1407                }
1408                else
1409                {
1410                    metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]);
1411                    if (metadataDef != null)
1412                    {
1413                        definitions.add(metadataDef);
1414                    }
1415                    else
1416                    {
1417                        return Collections.emptyList();
1418                    }
1419                }
1420            }
1421        }
1422        
1423        return definitions;
1424    }
1425    
1426    /**
1427     * Get a flat map of MetadataDefinition path corresponding to a set of metadata paths
1428     * @param metadataPaths A set of metadata path to analyse
1429     * @param initialContentType The initial content type to start the search
1430     * @return The flat map of metadatapath and corresponding list of MetadataDefinition path
1431     * @deprecated Use {@link AttributeDefinition} API instead
1432     */
1433    @Deprecated
1434    public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(Set<String> metadataPaths, ContentType initialContentType)
1435    {
1436        Map<String, List<MetadataDefinition>> results = new HashMap<>();
1437        
1438        for (String metadataPath : metadataPaths)
1439        {
1440            results.put(metadataPath, getMetadataDefinitionPath(metadataPath, initialContentType));
1441        }
1442        
1443        return results;
1444    }
1445    
1446    /**
1447     * Get a flat map of MetadataDefinition path corresponding to a metadataSet
1448     * @param metadataSet A semetadataSet to analyse
1449     * @param initialContentType The initial content type to start the search
1450     * @return The flat map of metadatapath and corresponding list of MetadataDefinition path
1451     * @deprecated Use {@link AttributeDefinition} API instead
1452     */
1453    @Deprecated
1454    public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(MetadataSet metadataSet, ContentType initialContentType)
1455    {
1456        return getMetadataDefinitionsPaths(getMetadataPaths(metadataSet), initialContentType);
1457    }
1458    
1459    /**
1460     * Determines if the given content type can be added to content
1461     * 
1462     * @param content The content
1463     * @param cTypeId The id of content type
1464     * @return <code>true</code> if the content type is compatible with content
1465     */
1466    public boolean isCompatibleContentType(Content content, String cTypeId)
1467    {
1468        String[] currentContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes());
1469
1470        ArrayList<String> cTypes = new ArrayList<>(Arrays.asList(currentContentTypes));
1471        cTypes.add(cTypeId);
1472
1473        try
1474        {
1475            getMetadataDefinitions(cTypes.toArray(new String[cTypes.size()]));
1476            return true;
1477        }
1478        catch (ConfigurationException e)
1479        {
1480            return false;
1481        }
1482    }
1483
1484    /**
1485     * Retrieves all definitions of a metadata resulting of the concatenation of
1486     * metadata of given content types.
1487     * 
1488     * @param cTypes The id of content types
1489     * @return the metadata definitions
1490     * @throws ConfigurationException if an error occurred
1491     * @deprecated Use {@link AttributeDefinition} API instead
1492     */
1493    @Deprecated
1494    public Map<String, MetadataDefinition> getMetadataDefinitions(String[] cTypes) throws ConfigurationException
1495    {
1496        Map<String, MetadataDefinition> metadata = new LinkedHashMap<>();
1497
1498        for (String id : cTypes)
1499        {
1500            ContentType cType = _getContentTypeEP().getExtension(id);
1501
1502            for (String name : cType.getMetadataNames())
1503            {
1504                MetadataDefinition definition = cType.getMetadataDefinition(name);
1505
1506                if (metadata.containsKey(name))
1507                {
1508                    if (!definition.getReferenceContentType().equals(metadata.get(name).getReferenceContentType()))
1509                    {
1510                        // The definition does not provide from a common ancestor
1511                        throw new ConfigurationException("The metadata '" + name + "' defined in content-type '" + id + "' is already defined in another co-super-type '"
1512                                + metadata.get(name).getReferenceContentType() + "'");
1513                    }
1514                    continue;
1515                }
1516
1517                metadata.put(name, definition);
1518            }
1519        }
1520
1521        return metadata;
1522    }
1523    
1524    /**
1525     * Retrieves all model items of given content types.
1526     * @param contentTypes The identifier of the content types
1527     * @return the model items
1528     * @throws ConfigurationException if an error occurred
1529     */
1530    public Map<String, ModelItem> getModelItems(String[] contentTypes) throws ConfigurationException
1531    {
1532        Map<String, ModelItem> items = new LinkedHashMap<>();
1533
1534        for (String id : contentTypes)
1535        {
1536            ContentType contentType = _getContentTypeEP().getExtension(id);
1537
1538            for (ModelItem currentItem : contentType.getModelItems())
1539            {
1540                final String currentItemName = currentItem.getName();
1541                if (items.containsKey(currentItemName))
1542                {
1543                    ModelItem existingItem = items.get(currentItemName);
1544                    
1545                    if (!currentItem.getModel().equals(existingItem.getModel()))
1546                    {
1547                        // The definition does not provide from a common ancestor
1548                        throw new ConfigurationException("The metadata '" + currentItemName + "' defined in content-type '" + id + "' is already defined in another co-super-type '"
1549                                + existingItem.getModel() + "'");
1550                    }
1551                    continue;
1552                }
1553
1554                items.put(currentItemName, currentItem);
1555            }
1556        }
1557
1558        return items;
1559    }
1560    
1561    /**
1562     * Retrieves the common metadata definitions for a list of content types
1563     * @param cTypeIds The list of content types to consider
1564     * @param metadataSetName The metadata set name to list metadata
1565     * @param isEdition Is the metadata set for edition (or for view)
1566     * @return The map of metadata definition. Key are the metadata path in the content type
1567     * @deprecated Use {@link AttributeDefinition} API instead
1568     */
1569    @Deprecated
1570    public Map<String, MetadataDefinition> getCommonMetadataDefinitions(Collection<String> cTypeIds, String metadataSetName, boolean isEdition)
1571    {
1572        Map<String, MetadataDefinition> commonMetadataDefinitions = null;
1573        
1574        for (String cTypeId : cTypeIds)
1575        {
1576            ContentType cType = _getContentTypeEP().getExtension(cTypeId);
1577            AbstractMetadataSetElement metadataSetElement = _getMetadataSet(cType, metadataSetName, isEdition);
1578            
1579            Map<String, MetadataDefinition> accumulator = new LinkedHashMap<>();
1580            if (metadataSetElement != null)
1581            {
1582                _getMetadataDefinitionsAcc(accumulator, metadataSetElement, "", cType);
1583            }
1584            
1585            if (commonMetadataDefinitions == null)
1586            {
1587                commonMetadataDefinitions = new LinkedHashMap<>();
1588                for (String name : accumulator.keySet())
1589                {
1590                    commonMetadataDefinitions.put(name, accumulator.get(name));
1591                }
1592            }
1593            else
1594            {
1595                // only retains common metadata (performs a set intersection)
1596                commonMetadataDefinitions.keySet().retainAll(accumulator.keySet());
1597            }
1598        }
1599        
1600        return commonMetadataDefinitions != null ? commonMetadataDefinitions : Collections.emptyMap();
1601    }
1602    
1603    /**
1604     * Populate the accumulator with the metadata definition
1605     * @param internalAcc the accumulator of metadata definition
1606     * @param metadataSetElement the metadata set of the content
1607     * @param prefix the path prefix preceding the names of the metadata
1608     * @param metaDefHolder the holder of the metadata definitions for the content
1609     * @deprecated Use {@link AttributeDefinition} API instead
1610     */
1611    @Deprecated
1612    protected void _getMetadataDefinitionsAcc(Map<String, MetadataDefinition> internalAcc, AbstractMetadataSetElement metadataSetElement, String prefix, MetadataDefinitionHolder metaDefHolder)
1613    {
1614        if (metadataSetElement instanceof MetadataDefinitionReference)
1615        {
1616            MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) metadataSetElement;
1617            String subMetadataName = metadataDefRef.getMetadataName();
1618            MetadataDefinition subMetadataDef = metaDefHolder.getMetadataDefinition(subMetadataName);
1619
1620            if (subMetadataDef != null)
1621            {
1622                if (MetadataType.COMPOSITE.equals(subMetadataDef.getType()))
1623                {
1624                    for (AbstractMetadataSetElement subElement : metadataSetElement.getElements())
1625                    {
1626                        String path = prefix + subMetadataName;
1627                        internalAcc.put(path, subMetadataDef);
1628                        _getMetadataDefinitionsAcc(internalAcc, subElement, path + "/", subMetadataDef);
1629                    }
1630                }
1631                else
1632                {
1633                    String path = prefix + subMetadataName;
1634                    internalAcc.put(path, subMetadataDef);
1635                }
1636            }
1637        }
1638        else
1639        {
1640            for (AbstractMetadataSetElement subElement : metadataSetElement.getElements())
1641            {
1642                _getMetadataDefinitionsAcc(internalAcc, subElement, prefix, metaDefHolder);
1643            }
1644        }
1645    }
1646    
1647    /**
1648     * Get the metadataset of a content given the parameters
1649     * @param contentType The content type to consider
1650     * @param metadataSetName The metadata set to get (that list the metadata to consider)
1651     * @param isEdition Is the metadata set for edition (or for view)
1652     * @return The metadataset
1653     * @deprecated Use {@link AttributeDefinition} API instead
1654     */
1655    @Deprecated
1656    protected MetadataSet _getMetadataSet(ContentType contentType, String metadataSetName, boolean isEdition)
1657    {
1658        String name = metadataSetName;
1659        if (StringUtils.isBlank(name))
1660        {
1661            name = "main";
1662        }
1663        
1664        MetadataSet metadataSet = null;
1665        
1666        if (isEdition)
1667        {
1668            metadataSet = contentType.getMetadataSetForEdition(name);
1669        }
1670        else
1671        {
1672            metadataSet = contentType.getMetadataSetForView(name);
1673        }
1674        
1675        return metadataSet;
1676    }
1677
1678    /**
1679     * Get information on content types.<br>
1680     * @return A Map with content types
1681     */
1682    public Set<Map<String, Object>> getContentTypesInformations()
1683    {
1684        Set<Map<String, Object>> result = new HashSet<>();
1685        
1686        // Collect content types.
1687        Collection<String> allContentTypesIds = _getContentTypeEP().getExtensionsIds();
1688        for (String id : allContentTypesIds)
1689        {
1690            ContentType cType = _getContentTypeEP().getExtension(id);
1691
1692            if (cType != null)
1693            {
1694                Map<String, Object> contentTypeProperties = getContentTypeProperties(cType);
1695                contentTypeProperties.put("rightEvaluated", _hasRight(cType));
1696                result.add(contentTypeProperties);
1697            }
1698        }
1699        
1700        return result;
1701    }
1702    
1703    /**
1704     * Get information on content types.<br>
1705     * 
1706     * @param ids The id of content types to retrieve
1707     * @param inherited If true, the sub-types will be also returned.
1708     * @param checkRights If true, only content types allowed for creation will
1709     *            be returned
1710     * @param includePrivate If true, the list will include the private content types. By default the list is restricted to the public content types.
1711     * @param includeMixins If true the list will include the mixins content types.
1712     * @param includeAbstract If true the list will include the abstract content types.
1713     * @return A Map with retrieved, unknown, not-allowed and private content types
1714     */
1715    @Callable
1716    public Map<String, Object> getContentTypesList (List<String> ids, boolean inherited, boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract)
1717    {
1718        // Collect content types.
1719        Collection<String> allContentTypesIds = new ArrayList<>();
1720        if (ids == null || ids.isEmpty())
1721        {
1722            allContentTypesIds = _getContentTypeEP().getExtensionsIds();
1723        }
1724        else
1725        {
1726            for (String id : ids)
1727            {
1728                _addIfNotPresent(allContentTypesIds, id);
1729    
1730                if (inherited)
1731                {
1732                    for (String subTypeId : _getContentTypeEP().getSubTypes(id))
1733                    {
1734                        _addIfNotPresent(allContentTypesIds, subTypeId);
1735                    }
1736                }
1737            }
1738        }
1739
1740        // Resolve and organize content types
1741        return _dispatchContentTypes(checkRights, includePrivate, includeMixins, includeAbstract, allContentTypesIds);
1742    }
1743
1744    private Map<String, Object> _dispatchContentTypes(boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract, Collection<String> allContentTypesIds)
1745    {
1746        List<Map<String, Object>> contentTypes = new ArrayList<>();
1747        List<String> unknownContentTypes = new ArrayList<>();
1748        List<String> noRightContentTypes = new ArrayList<>();
1749        List<String> privateContentTypes = new ArrayList<>();
1750        List<String> mixinContentTypes = new ArrayList<>();
1751        List<String> abstractContentTypes = new ArrayList<>();
1752
1753        for (String id : allContentTypesIds)
1754        {
1755            ContentType cType = _getContentTypeEP().getExtension(id);
1756
1757            if (cType != null)
1758            {
1759                if (cType.isAbstract() && !includeAbstract)
1760                {
1761                    abstractContentTypes.add(id);
1762                }
1763                else if (cType.isPrivate() && !includePrivate)
1764                {
1765                    privateContentTypes.add(id);
1766                }
1767                else if (cType.isMixin() && !includeMixins)
1768                {
1769                    mixinContentTypes.add(id);
1770                }
1771                else if (!checkRights || _hasRight(cType))
1772                {
1773                    contentTypes.add(getContentTypeProperties(cType));
1774                }
1775                else
1776                {
1777                    noRightContentTypes.add(id);
1778                }
1779            }
1780            else
1781            {
1782                unknownContentTypes.add(id);
1783            }
1784        }
1785        
1786        Map<String, Object> result = new HashMap<>();
1787
1788        result.put("contentTypes", contentTypes);
1789        result.put("noRightContentTypes", noRightContentTypes);
1790        result.put("unknownContentTypes", unknownContentTypes);
1791        result.put("privateContentTypes", privateContentTypes);
1792        result.put("mixinContentTypes", mixinContentTypes);
1793        result.put("abstractContentTypes", abstractContentTypes);
1794
1795        return result;
1796    }
1797
1798    /**
1799     * Get the content type properties
1800     * 
1801     * @param contentType The content type
1802     * @return The content type properties
1803     */
1804    public Map<String, Object> getContentTypeProperties(ContentType contentType)
1805    {
1806        Map<String, Object> infos = new HashMap<>();
1807
1808        infos.put("id", contentType.getId());
1809        infos.put("label", contentType.getLabel());
1810        infos.put("description", contentType.getDescription());
1811        infos.put("defaultTitle", contentType.getDefaultTitle());
1812        infos.put("iconGlyph", contentType.getIconGlyph());
1813        infos.put("iconDecorator", contentType.getIconDecorator());
1814        infos.put("iconSmall", contentType.getSmallIcon());
1815        infos.put("iconMedium", contentType.getMediumIcon());
1816        infos.put("iconLarge", contentType.getLargeIcon());
1817        infos.put("right", contentType.getRight());
1818        infos.put("isMultilingual", contentType.isMultilingual());
1819        infos.put("isSimple", contentType.isSimple());
1820        infos.put("isPrivate", contentType.isPrivate());
1821        infos.put("isAbstract", contentType.isAbstract());
1822        infos.put("isReferenceTable", contentType.isReferenceTable());
1823        infos.put("isMixin", contentType.isMixin());
1824        infos.put("superTypes", contentType.getSupertypeIds());
1825        infos.put("tags", contentType.getTags());
1826        infos.put("parentMetadataName", Optional.ofNullable(contentType.getParentMetadata()).map(MetadataDefinition::getId).orElse(""));
1827        
1828        return infos;
1829    }
1830
1831    /**
1832     * Test if the current user has the right needed by the content type to create a content.
1833     * @param contentType The content type
1834     * @return true if the user has the right needed, false otherwise.
1835     */
1836    protected boolean _hasRight(ContentType contentType)
1837    {
1838        boolean hasRight = false;
1839
1840        String right = contentType.getRight();
1841
1842        if (right == null)
1843        {
1844            hasRight = true;
1845        }
1846        else
1847        {
1848            UserIdentity user = _userProvider.getUser();
1849            hasRight = _rightManager.hasRight(user, right, "/cms") == RightResult.RIGHT_ALLOW || _rightManager.hasRight(user, right, _rootContentHelper.getRootContent()) == RightResult.RIGHT_ALLOW;
1850        }
1851
1852        return hasRight;
1853    }
1854
1855    private void _addIfNotPresent(Collection<String> collection, String value)
1856    {
1857        if (!collection.contains(value))
1858        {
1859            collection.add(value);
1860        }
1861    }
1862
1863    /**
1864     * Determine whether a metadata can be read at this time.
1865     * 
1866     * @param metadataDef the metadata definition
1867     * @param content The content where metadata is to be read on.
1868     * @return <code>true</code> if the current user is allowed to read the
1869     *         metadata of this content.
1870     * @throws AmetysRepositoryException if an error occurs while accessing the
1871     *             content.
1872     * @deprecated Use {@link AttributeDefinition} API instead
1873     */
1874    @Deprecated
1875    public boolean canRead(Content content, MetadataDefinition metadataDef)
1876    {
1877        String id = metadataDef.getReferenceContentType();
1878        ContentType cType = _getContentTypeEP().getExtension(id);
1879        return cType.canRead(content, metadataDef);
1880    }
1881
1882    /**
1883     * Determine whether a metadata can be read at this time.
1884     * 
1885     * @param metadataDef the metadata definition
1886     * @param content The content where metadata is to be read on.
1887     * @return <code>true</code> if the current user is allowed to read the
1888     *         metadata of this content.
1889     * @throws AmetysRepositoryException if an error occurs while accessing the
1890     *             content.
1891     * @deprecated Use {@link AttributeDefinition} API instead
1892     */
1893    @Deprecated
1894    public boolean canWrite(Content content, MetadataDefinition metadataDef)
1895    {
1896        String id = metadataDef.getReferenceContentType();
1897        ContentType cType = _getContentTypeEP().getExtension(id);
1898        return cType.canWrite(content, metadataDef);
1899    }
1900
1901    /**
1902     * Get the id of content type to use for rendering
1903     * 
1904     * @param content The content
1905     * @return the id of dynamic or standard content type
1906     */
1907    public String getContentTypeIdForRendering(Content content)
1908    {
1909        String dynamicContentTypeId = getDynamicContentTypeId(content.getTypes(), content.getMixinTypes());
1910        if (dynamicContentTypeId != null)
1911        {
1912            return dynamicContentTypeId;
1913        }
1914        
1915        ContentType firstContentType = getFirstContentType(content);
1916        return firstContentType != null ? firstContentType.getId() : StringUtils.EMPTY;
1917    }
1918
1919    /**
1920     * Get the id of the dynamic content type matching to given ones
1921     * @param contentTypes The content types
1922     * @param mixinTypes The mixins
1923     * @return the id of dynamic content type or null if no dynamic content type was found
1924     */
1925    public String getDynamicContentTypeId(String[] contentTypes, String[] mixinTypes)
1926    {
1927        DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(contentTypes, mixinTypes);
1928        if (dynamicContentType != null)
1929        {
1930            return dynamicContentType.getId();
1931        }
1932        
1933        return null;
1934    }
1935
1936    /**
1937     * Get the plugin name of content type to use for rendering
1938     * 
1939     * @param content The content
1940     * @return the plugin name of dynamic or standard content type
1941     */
1942    public String getContentTypePluginForRendering(Content content)
1943    {
1944        String dynamicContentTypePlugin = getDynamicContentTypePlugin(content.getTypes(), content.getMixinTypes());
1945        if (dynamicContentTypePlugin != null)
1946        {
1947            return dynamicContentTypePlugin;
1948        }
1949        
1950        ContentType firstContentType = getFirstContentType(content);
1951        return firstContentType != null ? firstContentType.getPluginName() : StringUtils.EMPTY;
1952    }
1953
1954    /**
1955     * Get the plugin name of the dynamic content type matching to given ones
1956     * @param contentTypes The content types
1957     * @param mixinTypes The mixins
1958     * @return the plugin name of dynamic content type or null if no dynamic content type was found
1959     */
1960    public String getDynamicContentTypePlugin(String[] contentTypes, String[] mixinTypes)
1961    {
1962        DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(contentTypes, mixinTypes);
1963        if (dynamicContentType != null)
1964        {
1965            return dynamicContentType.getPluginName();
1966        }
1967        
1968        return null;
1969    }
1970
1971    /**
1972     * Retrieves the label of the content type.
1973     * 
1974     * @param content The content
1975     * @return the label.
1976     */
1977    public I18nizableText getContentTypeLabel(Content content)
1978    {
1979        DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
1980        if (dynamicContentType != null)
1981        {
1982            return dynamicContentType.getLabel();
1983        }
1984        
1985        ContentType firstContentType = getFirstContentType(content);
1986        return firstContentType != null ? firstContentType.getLabel() : new I18nizableText(StringUtils.EMPTY);
1987    }
1988
1989    /**
1990     * Retrieves the description of the content type.
1991     * 
1992     * @param content The content
1993     * @return the label.
1994     */
1995    public I18nizableText getContentTypeDescription(Content content)
1996    {
1997        DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
1998        if (dynamicContentType != null)
1999        {
2000            return dynamicContentType.getDescription();
2001        }
2002        
2003        ContentType firstContentType = getFirstContentType(content);
2004        return firstContentType != null ? firstContentType.getDescription() : new I18nizableText(StringUtils.EMPTY);
2005    }
2006
2007    /**
2008     * Retrieves the default title of the content type.
2009     * 
2010     * @param content The content
2011     * @return the label.
2012     */
2013    public I18nizableText getContentTypeDefaultTitle(Content content)
2014    {
2015        DynamicContentTypeDescriptor dynamicContentType = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
2016        if (dynamicContentType != null)
2017        {
2018            return dynamicContentType.getDefaultTitle();
2019        }
2020
2021        ContentType firstContentType = getFirstContentType(content);
2022        return firstContentType != null ? firstContentType.getDefaultTitle() : new I18nizableText(StringUtils.EMPTY);
2023    }
2024
2025    /**
2026     * Retrieves the category of the content type.
2027     * 
2028     * @param content The content
2029     * @return the label.
2030     */
2031    public I18nizableText getContentTypeCategory(Content content)
2032    {
2033        DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
2034        if (dynamicCTDescriptor != null)
2035        {
2036            return dynamicCTDescriptor.getCategory();
2037        }
2038        
2039        ContentType firstContentType = getFirstContentType(content);
2040        return firstContentType != null ? firstContentType.getCategory() : new I18nizableText(StringUtils.EMPTY);
2041    }
2042    
2043    /**
2044     * Retrieves the CSS class to use as glyph icon of the content
2045     * @param content The content
2046     * @return the glyph
2047     */
2048    public String getIconGlyph(Content content)
2049    {
2050        DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
2051        if (dynamicCTDescriptor != null)
2052        {
2053            return dynamicCTDescriptor.getIconGlyph();
2054        }
2055        
2056        ContentType firstContentType = getFirstContentType(content);
2057        return firstContentType != null ? firstContentType.getIconGlyph() : null;
2058    }
2059    
2060    /**
2061     * Retrieves the CSS class to use as decorator above the main icon
2062     * @param content The content
2063     * @return the decorator CSS class name
2064     */
2065    public String getIconDecorator(Content content)
2066    {
2067        DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
2068        if (dynamicCTDescriptor != null)
2069        {
2070            return dynamicCTDescriptor.getIconDecorator();
2071        }
2072        
2073        ContentType firstContentType = getFirstContentType(content);
2074        return firstContentType != null ? firstContentType.getIconDecorator() : null;
2075    }
2076
2077    /**
2078     * Retrieves the URL of the icon without the context path.
2079     * 
2080     * @param content The content
2081     * @return the icon URL for the small image 16x16.
2082     */
2083    public String getSmallIcon(Content content)
2084    {
2085        DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
2086        if (dynamicCTDescriptor != null)
2087        {
2088            return dynamicCTDescriptor.getSmallIcon();
2089        }
2090        
2091        ContentType firstContentType = getFirstContentType(content);
2092        return firstContentType != null ? firstContentType.getSmallIcon() : StringUtils.EMPTY;
2093    }
2094
2095    /**
2096     * Retrieves the URL of the icon without the context path.
2097     * 
2098     * @param content The content
2099     * @return the icon URL for the medium image 32x32.
2100     */
2101    public String getMediumIcon(Content content)
2102    {
2103        DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
2104        if (dynamicCTDescriptor != null)
2105        {
2106            return dynamicCTDescriptor.getMediumIcon();
2107        }
2108        
2109        ContentType firstContentType = getFirstContentType(content);
2110        return firstContentType != null ? firstContentType.getMediumIcon() : StringUtils.EMPTY;
2111    }
2112
2113    /**
2114     * Retrieves the URL of the icon without the context path.
2115     * 
2116     * @param content The content
2117     * @return the icon URL for the large image 48x48.
2118     */
2119    public String getLargeIcon(Content content)
2120    {
2121        DynamicContentTypeDescriptor dynamicCTDescriptor = _getDynamicContentTypeDescriptorExtentionPoint().getMatchingDescriptor(content.getTypes(), content.getMixinTypes());
2122        if (dynamicCTDescriptor != null)
2123        {
2124            return dynamicCTDescriptor.getLargeIcon();
2125        }
2126        
2127        ContentType firstContentType = getFirstContentType(content);
2128        return firstContentType != null ? firstContentType.getLargeIcon() : StringUtils.EMPTY;
2129    }
2130
2131    /**
2132     * Get the content type which determines the content icons and rendering
2133     * 
2134     * @param content The content
2135     * @return The main content type
2136     */
2137    public ContentType getFirstContentType(Content content)
2138    {
2139        TreeSet<ContentType> treeSet = new TreeSet<>(new ContentTypeComparator());
2140
2141        for (String id : content.getTypes())
2142        {
2143            ContentType contentType = _getContentTypeEP().getExtension(id);
2144            if (contentType != null)
2145            {
2146                treeSet.add(contentType);
2147            }
2148            else
2149            {
2150                if (getLogger().isWarnEnabled())
2151                {
2152                    getLogger().warn(String.format("Trying to get an unknown content type : '%s'.", id));
2153                }
2154            }
2155        }
2156        
2157        return !treeSet.isEmpty() ? treeSet.first() : null;
2158    }
2159    
2160    /**
2161     * Get the metadata definition for the "title" standard metadata.
2162     * @return The standard title metadata definition.
2163     * @deprecated Use {@link AttributeDefinition} API instead
2164     */
2165    @Deprecated
2166    public static MetadataDefinition getTitleMetadataDefinition()
2167    {
2168        MetadataDefinition def = new MetadataDefinition();
2169        def.setId("title");
2170        def.setName("title");
2171        def.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_LABEL"));
2172        def.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_DESCRIPTION"));
2173        def.setType(MetadataType.STRING);
2174        def.setMultiple(false);
2175        
2176        return def;
2177    }
2178
2179    class ContentTypeComparator implements Comparator<ContentType>
2180    {
2181        @Override
2182        public int compare(ContentType c1, ContentType c2)
2183        {
2184            I18nizableText t1 = c1.getLabel();
2185            I18nizableText t2 = c2.getLabel();
2186
2187            String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel();
2188            String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel();
2189
2190            int compareTo = str1.toString().compareTo(str2.toString());
2191            if (compareTo == 0)
2192            {
2193                // Content types have same keys but there are not equals, so do
2194                // not return 0 to add it in TreeSet
2195                // Indeed, in a TreeSet implementation two elements that are
2196                // equal by the method compareTo are, from the standpoint of the
2197                // set, equal
2198                return 1;
2199            }
2200            return compareTo;
2201        }
2202    }
2203
2204    private String _getCacheIdentifier(String[] contentTypes, String[] mixins)
2205    {
2206        String[] sortedContentTypes = contentTypes.clone();
2207        Arrays.sort(sortedContentTypes);
2208        String name = StringUtils.join(sortedContentTypes, ";");
2209
2210        if (mixins.length > 0)
2211        {
2212            String[] sortedMixins = mixins.clone();
2213            Arrays.sort(sortedMixins);
2214            name += ";";
2215            name += StringUtils.join(mixins, ";");
2216        }
2217
2218        return name;
2219    }
2220}