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