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}