001/*
002 *  Copyright 2017 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.HashMap;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.logger.AbstractLogEnabled;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033
034import org.ametys.core.ui.Callable;
035import org.ametys.runtime.i18n.I18nizableText;
036
037/**
038 * This component get content types according to a lot of parameters (hide mixin content type, hide non reference content type...)
039 */
040public class ContentTypesTreeComponent extends AbstractLogEnabled implements Component, Serviceable
041{
042    /** Avalon Role. */
043    public static final String ROLE = ContentTypesTreeComponent.class.getName();
044    
045    private ContentTypeExtensionPoint _contentTypeEP;
046    
047    public void service(ServiceManager smanager) throws ServiceException
048    {
049        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
050    }
051    
052    enum ExcludeMode
053    {
054        DISABLED,
055        HIDE
056    }
057    
058    /**
059     * Get content types to hierarchical or flat representation according to given parameters
060     * @param params The parameters :<ul>
061     *  <li>{Boolean} excludeReferenceTable : to exclude reference table. Defaults to true.</li>
062     *  <li>{Boolean} excludePrivate : to exclude private content types. Defaults to false.</li>
063     *  <li>{Boolean} excludeMixin : to exclude mixins. Defaults to false.</li>
064     *  <li>{Boolean} excludeAbstract : to exclude asbtract content types. Defaults to false.</li>
065     *  <li>{String}  excludeMode : 'hide' or 'disabled'. The mode of exclusion. Defaults to 'disabled'.</li>
066     *  <li>{Boolean} hierarchicalView: true to get a hierarchical representation of content types, or false to get a flat representation. Defaults to true.</li>
067     *  <li>{List&lt;String&gt;} contentTypes: the ids of content types to restrict the list. The child content types will be also returned</li>
068     *  <li>{List&lt;String&gt;} strictContentTypes: the ids of content types to restrict the list. Only these content types will be returned</li></ul>
069     * @return The content types according to received parameters
070     */
071    @SuppressWarnings("unchecked")
072    @Callable
073    public Map<String, Object> getContentTypes(Map<String, Object> params)
074    {
075        Map<String, Object> result = new HashMap<>();
076        
077        boolean excludeReferenceTable = params.containsKey("excludeReferenceTable") ? (boolean) params.get("excludeReferenceTable") : true;
078        boolean excludePrivate = params.containsKey("excludePrivate") ? (boolean) params.get("excludePrivate") : false;
079        boolean excludeMixin = params.containsKey("excludeMixin") ? (boolean) params.get("excludeMixin") : false;
080        boolean excludeAbstract = params.containsKey("excludeAbstract") ? (boolean) params.get("excludeAbstract") : false;
081        boolean includeMixinOnly = params.containsKey("includeMixinOnly") ? (boolean) params.get("includeMixinOnly") : false;
082        
083        String excludeModeAsStr = params.containsKey("excludeMode") ? (String) params.get("excludeMode") : "disabled";
084        ExcludeMode excludeMode = ExcludeMode.valueOf(excludeModeAsStr.toUpperCase());
085        
086        boolean hierarchicalView = params.containsKey("hierarchicalView") ? (boolean) params.get("hierarchicalView") : true;
087        List<String> strictContentTypeIds = params.containsKey("strictContentTypes") ? (List<String>) params.get("strictContentTypes") : null;
088        List<String> contentTypeIds = params.containsKey("contentTypes") ? (List<String>) params.get("contentTypes") : null;
089        
090        if ((strictContentTypeIds == null || strictContentTypeIds.isEmpty()) && contentTypeIds != null && !contentTypeIds.isEmpty())
091        {
092            strictContentTypeIds = _getRestrictedContentTypes(contentTypeIds);
093        }
094        
095        List<Map<String, Object>> cTypes = _getContentTypes(excludeReferenceTable, excludePrivate, excludeMixin, excludeAbstract, includeMixinOnly, excludeMode, hierarchicalView, strictContentTypeIds);
096        
097        result.put("children", cTypes);
098        return result;
099    }
100    
101    private List<Map<String, Object>> _getContentTypes(boolean excludeReferenceTable, boolean excludePrivate, boolean excludeMixin, boolean excludeAbstract, boolean includeMixinOnly, ExcludeMode excludeMode, boolean hierarchicalView, List<String> strictContentTypeIds)
102    {
103        List<Map<String, Object>> cTypes = new ArrayList<>();
104        
105        if (hierarchicalView)
106        {
107            Set<ContentType> rootCTypes = _getRootContentTypes();
108            for (ContentType rootCType : rootCTypes)
109            {
110                cTypes.addAll(_getHierarchicalContentType(rootCType, strictContentTypeIds, excludeReferenceTable, excludePrivate, excludeMixin, excludeAbstract, includeMixinOnly, excludeMode));
111            }
112        }
113        else
114        {
115            Set<ContentType> rootCTypes = _getContentTypes();
116            for (ContentType rootCType : rootCTypes)
117            {
118                cTypes.addAll(_getNonHierarchicalContentType(rootCType, strictContentTypeIds, excludeReferenceTable, excludePrivate, excludeMixin, excludeAbstract, includeMixinOnly));
119            }
120        }
121        
122        return cTypes;
123    }
124    
125    private List<String> _getRestrictedContentTypes(Collection<String> contentTypeIds)
126    {
127        Set<String> restrictedContentTypeIds = new HashSet<>();
128        restrictedContentTypeIds.addAll(contentTypeIds);
129        
130        for (String contentTypeId : contentTypeIds)
131        {
132            restrictedContentTypeIds.addAll(_contentTypeEP.getSubTypes(contentTypeId));
133        }
134        
135        return new ArrayList<>(restrictedContentTypeIds);
136    }
137    
138    private List<Map<String, Object>> _getHierarchicalContentType(ContentType contentType, Collection<String> restrictedContentTypes, boolean excludeReferenceTable, boolean excludePrivate, boolean excludeMixin, boolean excludeAbstract, boolean mixinOnly, ExcludeMode excludeMode)
139    {
140        List<Map<String, Object>> cTypes = new ArrayList<>();
141        
142        boolean matchCondition = _matchCondition(contentType, restrictedContentTypes, excludeReferenceTable, excludePrivate, excludeMixin, excludeAbstract, mixinOnly);
143        if (matchCondition || excludeMode == ExcludeMode.DISABLED)
144        {
145            Map<String, Object> params = _getContentTypeParams(contentType, !matchCondition);
146            
147            List<Map<String, Object>> children = new ArrayList<>();
148            Collection<String> childContentTypes = _contentTypeEP.getDirectSubTypes(contentType.getId());
149            for (String id : childContentTypes)
150            {
151                ContentType childContentType = _contentTypeEP.getExtension(id);
152                children.addAll(_getHierarchicalContentType(childContentType, restrictedContentTypes, excludeReferenceTable, excludePrivate, excludeMixin, excludeAbstract, mixinOnly, excludeMode));
153            }
154            
155            if (!children.isEmpty() || matchCondition)
156            {
157                params.put("leaf", children.isEmpty());
158                cTypes.add(params); 
159                
160                if (!children.isEmpty())
161                {
162                    params.put("children", children);
163                }
164            }
165          
166        }
167        
168        return cTypes;
169    }
170    
171    private List<Map<String, Object>> _getNonHierarchicalContentType(ContentType contentType, Collection<String> restrictedContentTypes, boolean excludeReferenceTable, boolean excludePrivate, boolean excludeMixin, boolean excludeAbstract, boolean mixinOnly)
172    {
173        List<Map<String, Object>> cTypes = new ArrayList<>();
174        boolean matchCondition = _matchCondition(contentType, restrictedContentTypes, excludeReferenceTable, excludePrivate, excludeMixin, excludeAbstract, mixinOnly);
175        if (matchCondition)
176        {
177            Map<String, Object> params = _getContentTypeParams(contentType, !matchCondition);
178            params.put("leaf", true);
179            cTypes.add(params); 
180        }
181        return cTypes;
182    }
183    
184    private Set<ContentType> _getRootContentTypes()
185    {
186        Set<ContentType> rootContentTypes = new HashSet<>();
187        
188        Set<String> ids = _contentTypeEP.getExtensionsIds();
189        for (String id : ids)
190        {
191            ContentType contentType = _contentTypeEP.getExtension(id);
192            if (contentType.getSupertypeIds().length == 0)
193            {
194                rootContentTypes.add(contentType);
195            }
196        }
197        return rootContentTypes;
198    }
199    
200    private Set<ContentType> _getContentTypes()
201    {
202        Set<ContentType> contentTypes = new HashSet<>();
203        
204        Set<String> ids = _contentTypeEP.getExtensionsIds();
205        for (String id : ids)
206        {
207            ContentType contentType = _contentTypeEP.getExtension(id);
208            contentTypes.add(contentType);
209        }
210        return contentTypes;
211    }
212    
213    private boolean _matchCondition(ContentType cType, Collection<String> restrictedContentTypes, boolean excludeReferenceTable, boolean excludePrivate, boolean excludeMixin, boolean excludeAbstract, boolean mixinOnly)
214    {
215        if (cType.isReferenceTable() && excludeReferenceTable
216                || cType.isPrivate() && excludePrivate
217                || cType.isMixin() && excludeMixin
218                || cType.isAbstract() && excludeAbstract
219                || !cType.isMixin() && mixinOnly
220                || (restrictedContentTypes != null && !restrictedContentTypes.isEmpty() && !restrictedContentTypes.contains(cType.getId())))
221        {
222            return false;
223        }
224        
225        return true;
226    }
227    
228    /**
229     * Get the matching content types
230     * @param excludeReferenceTable true to exclude reference table
231     * @param excludePrivate true to exclude the private content types
232     * @param excludeAbstract true to exclude the abstract content types
233     * @param excludeMixin true to exclude the mixins
234     * @param mixinOnly true to include the mixins only
235     * @return matching content types
236     */
237    public Set<ContentType> getMatchingContentTypes(boolean excludeReferenceTable, boolean excludePrivate, boolean excludeAbstract, boolean excludeMixin, boolean mixinOnly)
238    {
239        return getMatchingContentTypes(null, true, excludeReferenceTable, excludePrivate, excludeAbstract, excludeMixin, mixinOnly);
240    }
241    
242    /**
243     * Get the matching content types among the given list
244     * @param contentTypesIds The list of content types to browse. Can be null to browse all available content types
245     * @param browseChildren to browse the child content types
246     * @param excludeReferenceTable true to exclude reference table
247     * @param excludePrivate true to exclude the private content types
248     * @param excludeAbstract true to exclude the abstract content types
249     * @param excludeMixin true to exclude the mixins
250     * @param mixinOnly true to include the mixins only
251     * @return matching content types
252     */
253    public Set<ContentType> getMatchingContentTypes(String[] contentTypesIds, boolean browseChildren, boolean excludeReferenceTable, boolean excludePrivate, boolean excludeAbstract, boolean excludeMixin, boolean mixinOnly)
254    {
255        Set<ContentType> matchingContentTypes = new HashSet<>();
256        Set<String> ids;
257        if (contentTypesIds != null && contentTypesIds.length != 0)
258        {
259            ids = new HashSet<>(Arrays.asList(contentTypesIds));
260        }
261        else
262        {
263            ids = _contentTypeEP.getExtensionsIds();
264        }
265        
266        for (String id : ids)
267        {
268            ContentType contentType = _contentTypeEP.getExtension(id);
269            
270            if (_matchCondition(contentType, null, excludeReferenceTable, excludePrivate, excludeMixin, excludeAbstract, mixinOnly))
271            {
272                matchingContentTypes.add(contentType);
273            }
274            
275            if (browseChildren)
276            {
277                Collection<String> childIds = _contentTypeEP.getSubTypes(id);
278                for (String childId : childIds)
279                {
280                    matchingContentTypes.addAll(getMatchingContentTypes(new String[]{childId}, true, excludeReferenceTable, excludePrivate, excludeAbstract, excludeMixin, mixinOnly));
281                }
282            }
283        }
284        
285        return matchingContentTypes;
286    }
287    
288    private Map<String, Object> _getContentTypeParams(ContentType contentType, boolean disabled) 
289    {
290        Map<String, Object> params = new HashMap<>();
291        params.put("contentTypeId", contentType.getId());
292        params.put("text", contentType.getLabel());
293        params.put("label", contentType.getLabel());
294        params.put("description", contentType.getDescription());
295        params.put("abstract", contentType.isAbstract());
296        params.put("private", contentType.isPrivate());
297        params.put("reference-table", contentType.isReferenceTable());
298        params.put("mixin", contentType.isMixin());
299        params.put("simple", contentType.isSimple());
300        params.put("multilingual", contentType.isMultilingual());
301        params.put("iconGlyph", contentType.getIconGlyph());
302        params.put("iconDecorator", contentType.getIconDecorator());
303        params.put("iconSmall", contentType.getSmallIcon());
304        params.put("iconMedium", contentType.getMediumIcon());
305        params.put("iconLarge", contentType.getLargeIcon());
306        if (contentType instanceof AutomaticContentType)
307        {
308            params.put("origin", new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENTTYPE_TREE_CONF_ORIGIN"));
309            params.put("glyphOrigin", "ametysicon-xml6");
310        }
311        else
312        {
313            Map<String, I18nizableText> pluginName = Collections.singletonMap("pluginName", new I18nizableText(contentType.getPluginName()));
314            I18nizableText origin = new I18nizableText("plugin.cms", "PLUGINS_CMS_CONTENTTYPE_TREE_PLUGIN_ORIGIN", pluginName);
315            params.put("origin", origin);
316            params.put("glyphOrigin", "ametysicon-puzzle-piece1");
317        }
318        if (disabled)
319        {
320            params.put("disabled", true);
321        }
322        
323        return params;
324    }
325}