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