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