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.search;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Locale;
022import java.util.Map;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028
029import org.ametys.cms.content.ContentHelper;
030import org.ametys.cms.contenttype.ContentType;
031import org.ametys.cms.contenttype.MetadataDefinition;
032import org.ametys.cms.repository.Content;
033import org.ametys.cms.search.cocoon.GroupSearchContent;
034import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
035import org.ametys.core.user.User;
036import org.ametys.core.user.UserIdentity;
037import org.ametys.core.user.UserManager;
038import org.ametys.core.util.I18nUtils;
039import org.ametys.runtime.i18n.I18nizableText;
040import org.ametys.runtime.parameter.ParameterHelper;
041
042/**
043 * Helper to group search contents
044 */
045public class GroupSearchContentHelper implements Component, Serviceable
046{
047    /** The Avalon role name */
048    public static final String ROLE = GroupSearchContentHelper.class.getName();
049    
050    private SystemPropertyExtensionPoint _systemPropertyExtensionPoint;
051    private ContentHelper _contentHelper;
052    private UserManager _userManager;
053    private I18nUtils _i18nUtils;
054
055    public void service(ServiceManager serviceManager) throws ServiceException
056    {
057        _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) serviceManager.lookup(SystemPropertyExtensionPoint.ROLE);
058        _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE);
059        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
060        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
061    }
062    
063    /**
064     * Create GroupSearchContent to organize the contents in a hierarchy according to a list of metadata
065     * Example Continent/Country/City
066     * @param contents contents that need to be sorted
067     * @param groupingFields list of metadata to group with (ordered)
068     * @param contentType the content type
069     * @param locale The locale for groups. Can be null. Only useful if grouping by multilingual contents or multilingual string
070     * @return the root group of contents
071     */
072    public GroupSearchContent organizeContentsInGroups(Iterable<Content> contents, List<String> groupingFields, ContentType contentType, Locale locale)
073    {
074        GroupSearchContent groupSearchContent = new GroupSearchContent();
075        _organizeContents(groupSearchContent, contents, groupingFields, contentType, locale);
076        return groupSearchContent;
077    }
078    
079    /**
080     * Recursive function that create sub GroupSearchContent to organize the contents in a hierarchy according to a list of metadata
081     * Example Continent/Country/City
082     * @param parent parent group in which contents will be added, or in which new groups will be created
083     * @param contents contents that need to be sorted
084     * @param groupingFields list of metadata to group with (ordered)
085     * @param contentType the content type
086     * @param locale The local to get i18n titles
087     */
088    private void _organizeContents(GroupSearchContent parent, Iterable<Content> contents, List<String> groupingFields, ContentType contentType, Locale locale)
089    {
090        if (groupingFields != null && groupingFields.size() > 0)
091        {
092            String fieldId = groupingFields.get(0);
093            // Group contents by metadata value
094            Map<Object, List<Content>> groups = _groupBy(contents, fieldId, locale);
095
096            // Get the rest of the metadata regroupment list
097            List<String> subMetadataRegroupments = new ArrayList<>();
098            if (groupingFields.size() > 1)
099            {
100                subMetadataRegroupments.addAll(groupingFields);
101                subMetadataRegroupments.remove(0);
102            }
103            // Then : add sorted contents into groups, or re-organize if there are still some metadataRegroupments
104            for (Map.Entry<Object, List<Content>> entry : groups.entrySet())
105            {
106                String keyStr = _getGroupValueAsString(fieldId, entry.getKey(), contentType, locale);
107                
108                GroupSearchContent groupSearchContent = new GroupSearchContent(fieldId, keyStr);
109                if (groupingFields.size() == 1)
110                {
111                    // If no more groups-level, add all contents in the group
112                    groupSearchContent.addContents(entry.getValue());
113                }
114                else
115                {
116                    // If more groups to do : let's do it !
117                    _organizeContents(groupSearchContent, entry.getValue(), subMetadataRegroupments, contentType, locale);
118                }
119                // Add current group into parent
120                parent.addToSubList(groupSearchContent);
121            }
122        }
123        else
124        {
125            // If there are no group to do, just add all contents into the parent
126            for (Content content : contents)
127            {
128                parent.addContent(content);
129            }
130        }
131    }
132    
133    /**
134     * Group contents by given grouping field
135     * @param contents list of content
136     * @param fieldId The id of grouping field. Can be the path of a metadata or the system property
137     * @param locale The local to get i18n titles
138     * @return a Map where the key will be the value of the metadata
139     */
140    private Map<Object, List<Content>> _groupBy(Iterable<Content> contents, String fieldId, Locale locale)
141    {
142        Map<Object, List<Content>> groups = new HashMap<>();
143        for (Content content : contents)
144        {
145            Object value = _contentHelper.getValue(content, fieldId, locale, true);
146            if (value instanceof List)
147            {
148                // If the value is a list, the content is added into different groups, depending on the values in the list
149                List valueList = (List) value;
150                for (Object subValue : valueList)
151                {
152                    if (!groups.containsKey(subValue))
153                    {
154                        groups.put(subValue, new ArrayList<>());
155                    }
156                    groups.get(subValue).add(content);
157                }
158            }
159            else
160            {
161                if (!groups.containsKey(value))
162                {
163                    groups.put(value, new ArrayList<>());
164                }
165                groups.get(value).add(content);
166            }
167        }
168        return groups;
169    }
170    
171    private String _getGroupValueAsString(String fieldId, Object value, ContentType contentType, Locale locale)
172    {
173        if (value == null)
174        {
175            return "";
176        }
177        
178        if (value instanceof Content)
179        {
180            return ((Content) value).getTitle(locale);
181        }
182        else if (value instanceof UserIdentity)
183        {
184            User user = _userManager.getUser((UserIdentity) value);
185            return user != null ? user.getFullName() : UserIdentity.userIdentityToString((UserIdentity) value);
186        }
187        else if (!_systemPropertyExtensionPoint.hasExtension(fieldId))
188        {
189            String valueAsStr = ParameterHelper.valueToString(value);
190            
191            MetadataDefinition metadataDef = contentType != null ? contentType.getMetadataDefinitionByPath(fieldId) : null;
192            if (metadataDef != null && metadataDef.getEnumerator() != null)
193            {
194                try
195                {
196                    I18nizableText label = metadataDef.getEnumerator().getEntry(valueAsStr);
197                    if (label != null)
198                    {
199                        return _i18nUtils.translate(label);
200                    }
201                }
202                catch (Exception e)
203                {
204                    return valueAsStr;
205                }
206                
207            }
208        }
209        return ParameterHelper.valueToString(value);
210        
211    }
212}