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}