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