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