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