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; 024 025import org.apache.avalon.framework.configuration.Configuration; 026import org.apache.avalon.framework.configuration.ConfigurationException; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.xml.AttributesImpl; 030import org.apache.cocoon.xml.XMLUtils; 031import org.xml.sax.ContentHandler; 032 033import org.ametys.cms.contenttype.ContentType; 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.search.GroupSearchContentHelper; 036import org.ametys.cms.search.cocoon.GroupSearchContent; 037import org.ametys.cms.search.model.ResultField; 038import org.ametys.core.util.StringUtils; 039import org.ametys.plugins.extraction.ExtractionConstants; 040import org.ametys.plugins.extraction.execution.ExtractionExecutionContext; 041import org.ametys.plugins.repository.AmetysObjectIterable; 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 metadata types just to throw an Exception if the field is not available for content types 078 this._getMetadataType(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(AmetysObjectIterable<Content> contents, Locale defaultLocale) 089 { 090 String baseContentTypeId = _contentTypesHelper.getCommonAncestor(_contentTypes); 091 ContentType baseContentType = null; 092 if (baseContentTypeId != null) 093 { 094 baseContentType = _contentTypeExtensionPoint.getExtension(baseContentTypeId); 095 } 096 GroupSearchContent rootGroup = _groupSearchContentHelper.organizeContentsInGroups(contents, _groupingFields, baseContentType, defaultLocale); 097 return rootGroup; 098 } 099 100 /** 101 * Recursive method that saxes group of contents 102 * First level (level=0) will not create a xml group (usually, 1st group is just here to handle a list) 103 * @param contentHandler result document 104 * @param group group to sax 105 * @param level current level (start with zero) 106 * @param context execution context 107 * @param resultFields result fields to sax 108 * @throws Exception if a error occurred 109 */ 110 protected void saxGroup(ContentHandler contentHandler, GroupSearchContent group, int level, ExtractionExecutionContext context, Collection< ? extends ResultField> resultFields) throws Exception 111 { 112 if (level > 0) 113 { 114 AttributesImpl attrs = new AttributesImpl(); 115 attrs.addCDATAAttribute("level", String.valueOf(level)); 116 attrs.addCDATAAttribute("fieldPath", group.getGroupFieldPath()); 117 attrs.addCDATAAttribute("value", group.getGroupName()); 118 XMLUtils.startElement(contentHandler, "group", attrs); 119 } 120 if (group.getSubList() != null) 121 { 122 for (GroupSearchContent groupSearchContent : group.getSubList()) 123 { 124 saxGroup(contentHandler, groupSearchContent, level + 1, context, resultFields); 125 } 126 } 127 if (group.getContents() != null) 128 { 129 saxContents(contentHandler, context, resultFields, group.getContents()); 130 } 131 132 if (level > 0) 133 { 134 XMLUtils.endElement(contentHandler, "group"); 135 } 136 } 137 138 /** 139 * Sax a content 140 * @param contentHandler result document 141 * @param context execution context 142 * @param resultFields result fields to sax 143 * @param contents contents to sax 144 * @throws Exception if an error occurs 145 */ 146 protected abstract void saxContents(ContentHandler contentHandler, ExtractionExecutionContext context, Collection< ? extends ResultField> resultFields, List<Content> contents) throws Exception; 147 148 @Override 149 public Map<String, Object> getComponentDetailsForTree() 150 { 151 Map<String, Object> details = super.getComponentDetailsForTree(); 152 153 @SuppressWarnings("unchecked") 154 Map<String, Object> data = (Map<String, Object>) details.get("data"); 155 data.put("groupingFields", String.join(ExtractionConstants.STRING_COLLECTIONS_INPUT_DELEMITER, this.getGroupingFields())); 156 157 return details; 158 } 159 160 /** 161 * Retrieves the component grouping fields 162 * @return The component grouping fields 163 */ 164 public List<String> getGroupingFields() 165 { 166 return _groupingFields; 167 } 168 169 /** 170 * Add grouping fields to the component 171 * @param groupingFields Array of the grouping fields to add 172 */ 173 public void addGroupingFields(String... groupingFields) 174 { 175 _groupingFields.addAll(Arrays.asList(groupingFields)); 176 } 177}