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.cms.search.cocoon;
017
018import java.io.IOException;
019import java.util.ArrayList;
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.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.ProcessingException;
029import org.apache.cocoon.components.ContextHelper;
030import org.apache.cocoon.components.source.SourceUtil;
031import org.apache.cocoon.environment.Request;
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.lang3.StringUtils;
035import org.apache.excalibur.source.Source;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.contenttype.ContentType;
039import org.ametys.cms.repository.Content;
040import org.ametys.cms.search.GroupSearchContentHelper;
041import org.ametys.cms.search.SearchResults;
042import org.ametys.cms.search.model.SearchModel;
043import org.ametys.cms.search.ui.model.ColumnHelper.Column;
044import org.ametys.cms.search.ui.model.SearchUIModel;
045import org.ametys.core.util.IgnoreRootHandler;
046import org.ametys.plugins.repository.AmetysRepositoryException;
047import org.ametys.runtime.model.ViewItemContainer;
048
049/**
050 * Generate contents returned by the {@link SearchAction}, groups by selected fields.
051 */
052public class DocSearchGenerator extends SearchGenerator
053{
054    private GroupSearchContentHelper _groupSearchContentHelper;
055    
056    @Override
057    public void service(ServiceManager serviceManager) throws ServiceException
058    {
059        super.service(serviceManager);
060        _groupSearchContentHelper = (GroupSearchContentHelper) serviceManager.lookup(GroupSearchContentHelper.ROLE);
061    }
062    
063    @Override
064    protected void saxContents(SearchModel model, SearchResults<Content> results, String versionLabel, Map<String, Object> jsParameters, Locale locale, Map<String, Object> contextualParameters) throws SAXException, AmetysRepositoryException, IOException, ProcessingException
065    {
066        String modelId = (String) jsParameters.get("model");
067        SearchUIModel uiModel = _searchModelManager.getExtension(modelId);
068        
069        Set<String> cTypeIds = getCommonContentTypeIds(jsParameters, uiModel, contextualParameters);
070        
071        List<String> groupingFields = _getGroupingFields(jsParameters);
072
073        // Columns
074        List<Column> columns = getColumnsFromParameters(jsParameters, cTypeIds);
075        List<String> columnIds = columns.stream()
076                                        .map(Column::getId)
077                                        .collect(Collectors.toList());
078
079        ViewItemContainer resultItems = model.getResultItems(contextualParameters);
080        ViewItemContainer filteredResultItems = (ViewItemContainer) SearchGeneratorHelper.copyAndFilterViewItemAccessor(resultItems, columnIds);
081        SearchGeneratorHelper.removeResultItems(filteredResultItems, groupingFields);
082        
083        ViewItemContainer uiResultItems = _columnHelper.createViewFromColumns(cTypeIds, columns, false);
084        
085        Set<ContentType> contentTypes = cTypeIds.stream()
086                .map(_contentTypeExtensionPoint::getExtension)
087                .collect(Collectors.toSet());
088        GroupSearchContent groupSearchContent = _groupSearchContentHelper.organizeContentsInGroups(results.getObjects(), groupingFields, contentTypes, locale);
089        
090        // Set the request attributes from the contextual parameters
091        Request request = ContextHelper.getRequest(_context);
092        for (String name : contextualParameters.keySet())
093        {
094            request.setAttribute(name, contextualParameters.get(name));
095        }
096        
097        XMLUtils.startElement(contentHandler, "contents");
098        _saxGroup(groupSearchContent, 0, filteredResultItems, uiResultItems, locale);
099        XMLUtils.endElement(contentHandler, "contents");
100    }
101    
102    private List<String> _getGroupingFields(Map<String, Object> jsParameters)
103    {
104        @SuppressWarnings("unchecked")
105        List<String> groupingFields = (List<String>) jsParameters.get("groupingFields");
106        if (groupingFields == null || groupingFields.size() == 1 && StringUtils.isEmpty(groupingFields.get(0)))
107        {
108            groupingFields = new ArrayList<>();
109        }
110        
111        return groupingFields;
112    }
113    
114    /**
115     * Sax a group of content recursively.
116     * First level (level=0) will not create a xml group (usually, 1st group is just here to handle a list)
117     * @param group group to sax
118     * @param level current level (start with zero)
119     * @param resultItems result items to sax
120     * @param uiResultItems The columns to be exported
121     * @param locale The locale for search. Can be null.
122     * @throws SAXException if a error occurred during sax
123     * @throws AmetysRepositoryException if a error occurred
124     * @throws IOException if a error occurred
125     * @throws ProcessingException if a error occurred
126     */
127    private void _saxGroup(GroupSearchContent group, int level, ViewItemContainer resultItems, ViewItemContainer uiResultItems, Locale locale) throws SAXException, AmetysRepositoryException, IOException, ProcessingException
128    {
129        if (level > 0)
130        {
131            AttributesImpl attrs = new AttributesImpl();
132            attrs.addCDATAAttribute("level", String.valueOf(level));
133            attrs.addCDATAAttribute("fullModelPath", group.getGroupFieldPath());
134            attrs.addCDATAAttribute("value", group.getGroupName());
135            XMLUtils.startElement(contentHandler, "group", attrs);
136        }
137        
138        for (GroupSearchContent groupSearchContent : group.getSubList())
139        {
140            _saxGroup(groupSearchContent, level + 1, resultItems, uiResultItems, locale);
141        }
142        
143        for (Content content : group.getContents())
144        {
145            _saxContentForDoc(content, resultItems, uiResultItems, locale);
146        }
147        
148        if (level > 0)
149        {
150            XMLUtils.endElement(contentHandler, "group");
151        }
152    }
153    /**
154     * SAX the result content
155     * @param content the result
156     * @param resultItems the result items
157     * @param uiResultItems The columns to be exported
158     * @param locale The locale for search. Can be null.
159     * @throws SAXException if a error occurred during sax
160     * @throws AmetysRepositoryException if a error occurred
161     * @throws IOException if a error occurred
162     * @throws ProcessingException if a error occurred
163     */
164    private void _saxContentForDoc(Content content, ViewItemContainer resultItems, ViewItemContainer uiResultItems, Locale locale) throws SAXException, AmetysRepositoryException, IOException, ProcessingException
165    {
166        Request request = ContextHelper.getRequest(_context);
167        
168        try
169        {
170            request.setAttribute(Content.class.getName(), content);
171            request.setAttribute("resultItems", resultItems);
172            request.setAttribute("viewName", "export-word");
173            request.setAttribute("forceRemoteUrl", true);
174            
175            request.setAttribute("uiResultItems", uiResultItems);
176
177            AttributesImpl attrs = new AttributesImpl();
178            attrs.addCDATAAttribute("id", content.getId());
179            attrs.addCDATAAttribute("name", content.getName());
180            attrs.addCDATAAttribute("title", content.getTitle(locale));
181            if (content.getLanguage() != null)
182            {
183                attrs.addCDATAAttribute("language", content.getLanguage());
184            }
185                
186            XMLUtils.startElement(contentHandler, "content", attrs);
187            Source contentSource = resolver.resolveURI("cocoon:/export/content.doc" + (locale != null ? "?lang=" + locale.getLanguage() : ""));
188            SourceUtil.toSAX(contentSource, new IgnoreRootHandler(contentHandler));
189            
190            XMLUtils.endElement(contentHandler, "content");
191        }
192        finally
193        {
194            request.setAttribute(Content.class.getName(), null);
195            request.removeAttribute("forceRemoteUrl");
196        }
197    }
198    
199}