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.plugins.extraction.component; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.List; 022import java.util.Locale; 023import java.util.Map; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.xml.sax.ContentHandler; 034 035import org.ametys.cms.contenttype.ContentType; 036import org.ametys.cms.repository.Content; 037import org.ametys.cms.search.GroupSearchContentHelper; 038import org.ametys.cms.search.cocoon.GroupSearchContent; 039import org.ametys.cms.search.model.ResultField; 040import org.ametys.core.util.StringUtils; 041import org.ametys.plugins.extraction.ExtractionConstants; 042import org.ametys.plugins.extraction.execution.ExtractionExecutionContext; 043 044/** 045 * This class represents an extraction component with grouping fields 046 */ 047public abstract class AbstractGroupExtractionComponent extends AbstractSolrExtractionComponent 048{ 049 050 /** The list of grouping fields */ 051 protected List<String> _groupingFields = new ArrayList<>(); 052 053 private GroupSearchContentHelper _groupSearchContentHelper; 054 055 @Override 056 public void service(ServiceManager serviceManager) throws ServiceException 057 { 058 super.service(serviceManager); 059 _groupSearchContentHelper = (GroupSearchContentHelper) serviceManager.lookup(GroupSearchContentHelper.ROLE); 060 } 061 062 @Override 063 public void configure(Configuration configuration) throws ConfigurationException 064 { 065 super.configure(configuration); 066 067 String groupingFieldsAsString = configuration.getChild("grouping-fields").getValue(""); 068 _groupingFields.addAll(StringUtils.stringToCollection(groupingFieldsAsString)); 069 } 070 071 @Override 072 public void prepareComponentExecution(ExtractionExecutionContext context) throws Exception 073 { 074 super.prepareComponentExecution(context); 075 076 for (String groupingField : _groupingFields) 077 { 078 // get attribute type just to throw an Exception if the field is not available for content types 079 this._getAttributeTypeId(groupingField, _contentTypes); 080 } 081 } 082 083 /** 084 * Organizes contents in groups 085 * @param contents The contents to organize 086 * @param defaultLocale The default locale for localized values. Only useful if grouping by multilingual contents or multilingual string 087 * @return groups of content 088 */ 089 protected GroupSearchContent organizeContentsInGroups(Iterable<Content> contents, Locale defaultLocale) 090 { 091 Set<ContentType> baseContentTypes = _contentTypesHelper.getCommonAncestors(_contentTypes).stream() 092 .map(_contentTypeExtensionPoint::getExtension) 093 .collect(Collectors.toSet()); 094 return _groupSearchContentHelper.organizeContentsInGroups(contents, _groupingFields, baseContentTypes, defaultLocale); 095 } 096 097 /** 098 * Recursive method that saxes group of contents 099 * First level (level=0) will not create a xml group (usually, 1st group is just here to handle a list) 100 * @param contentHandler result document 101 * @param group group to sax 102 * @param level current level (start with zero) 103 * @param context execution context 104 * @param resultFields result fields to sax 105 * @throws Exception if a error occurred 106 */ 107 protected void saxGroup(ContentHandler contentHandler, GroupSearchContent group, int level, ExtractionExecutionContext context, Collection< ? extends ResultField> resultFields) throws Exception 108 { 109 if (level > 0) 110 { 111 AttributesImpl attrs = new AttributesImpl(); 112 attrs.addCDATAAttribute("level", String.valueOf(level)); 113 attrs.addCDATAAttribute("fullModelPath", group.getGroupFieldPath()); 114 attrs.addCDATAAttribute("value", group.getGroupName()); 115 XMLUtils.startElement(contentHandler, "group", attrs); 116 } 117 118 for (GroupSearchContent groupSearchContent : group.getSubList()) 119 { 120 saxGroup(contentHandler, groupSearchContent, level + 1, context, resultFields); 121 } 122 123 if (!group.getContents().isEmpty() || group.getSubList().isEmpty()) 124 { 125 // Generate SAX events for contents if there are contents or if there is no sub groups 126 saxContents(contentHandler, context, resultFields, group.getContents()); 127 } 128 129 if (level > 0) 130 { 131 XMLUtils.endElement(contentHandler, "group"); 132 } 133 } 134 135 /** 136 * Sax a content 137 * @param contentHandler result document 138 * @param context execution context 139 * @param resultFields result fields to sax 140 * @param contents contents to sax 141 * @throws Exception if an error occurs 142 */ 143 protected abstract void saxContents(ContentHandler contentHandler, ExtractionExecutionContext context, Collection< ? extends ResultField> resultFields, List<Content> contents) throws Exception; 144 145 @Override 146 public Map<String, Object> getComponentDetailsForTree() 147 { 148 Map<String, Object> details = super.getComponentDetailsForTree(); 149 150 @SuppressWarnings("unchecked") 151 Map<String, Object> data = (Map<String, Object>) details.get("data"); 152 data.put("groupingFields", String.join(ExtractionConstants.STRING_COLLECTIONS_INPUT_DELIMITER, this.getGroupingFields())); 153 154 return details; 155 } 156 157 /** 158 * Retrieves the component grouping fields 159 * @return The component grouping fields 160 */ 161 public List<String> getGroupingFields() 162 { 163 return _groupingFields; 164 } 165 166 /** 167 * Add grouping fields to the component 168 * @param groupingFields Array of the grouping fields to add 169 */ 170 public void addGroupingFields(String... groupingFields) 171 { 172 _groupingFields.addAll(Arrays.asList(groupingFields)); 173 } 174}