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}