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}