001/*
002 *  Copyright 2013 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.Collection;
021import java.util.Collections;
022import java.util.List;
023import java.util.Locale;
024import java.util.Map;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.cocoon.ProcessingException;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.ObjectModelHelper;
035import org.apache.cocoon.environment.Request;
036import org.apache.cocoon.generation.ServiceableGenerator;
037import org.apache.cocoon.xml.AttributesImpl;
038import org.apache.cocoon.xml.XMLUtils;
039import org.apache.commons.lang3.ArrayUtils;
040import org.apache.commons.lang3.StringUtils;
041import org.xml.sax.SAXException;
042
043import org.ametys.cms.repository.Content;
044import org.ametys.cms.search.SearchResult;
045import org.ametys.cms.search.SearchResults;
046import org.ametys.cms.search.content.ContentValuesExtractorFactory;
047import org.ametys.cms.search.model.ResultField;
048import org.ametys.cms.search.model.SearchModel;
049import org.ametys.cms.search.solr.CriteriaSearchUIModelWrapper.Column;
050import org.ametys.cms.search.solr.SolrQuerySearchAction;
051import org.ametys.cms.search.ui.model.SearchUIModelExtensionPoint;
052import org.ametys.core.util.ServerCommHelper;
053import org.ametys.plugins.repository.AmetysRepositoryException;
054import org.ametys.plugins.repository.version.VersionAwareAmetysObject;
055
056/**
057 * Generate contents returned by the {@link SearchAction}.
058 */
059public class SearchGenerator extends ServiceableGenerator implements Contextualizable
060{
061    /** Constant for getting content in specific version label */
062    public static final String CONTENT_VERSION_LABEL = "versionLabel";
063    
064    /** The server comm helper */
065    protected ServerCommHelper _serverCommHelper;
066    /** The content values extractor factory. */
067    protected ContentValuesExtractorFactory _valuesExtractorFactory; // FIXME not used
068    /** Helper for saxing result */
069    protected ContentResultSetHelper _contentRSH;
070    /** Context */
071    protected Context _context;
072    /** The search model manager */
073    protected SearchUIModelExtensionPoint _searchModelManager;
074    
075    @Override
076    public void service(ServiceManager smanager) throws ServiceException
077    {
078        super.service(smanager);
079        _serverCommHelper = (ServerCommHelper) smanager.lookup(ServerCommHelper.ROLE);
080        _valuesExtractorFactory = (ContentValuesExtractorFactory) smanager.lookup(ContentValuesExtractorFactory.ROLE);
081        
082        _contentRSH = (ContentResultSetHelper) smanager.lookup(ContentResultSetHelper.ROLE);
083        _searchModelManager = (SearchUIModelExtensionPoint) smanager.lookup(SearchUIModelExtensionPoint.ROLE);
084    }
085    
086    @Override
087    public void contextualize(Context context) throws ContextException
088    {
089        _context = context;
090    }
091    
092    @SuppressWarnings("unchecked")
093    @Override
094    public void generate() throws IOException, SAXException, ProcessingException
095    {
096        Request request = ObjectModelHelper.getRequest(objectModel);
097        
098        SearchResults<Content> results = (SearchResults<Content>) request.getAttribute(SearchAction.SEARCH_RESULTS);
099        SearchModel model = (SearchModel) request.getAttribute(SearchAction.SEARCH_MODEL);
100        
101        Map<String, Object> jsParameters = _serverCommHelper.getJsParameters();
102        
103        Map<String, Object> contextualParameters = (Map<String, Object>) jsParameters.get("contextualParameters");
104        if (contextualParameters == null)
105        {
106            contextualParameters = Collections.emptyMap();
107        }
108        
109        Locale defaultLocale = (Locale) request.getAttribute(SearchAction.SEARCH_LOCALE);
110        
111        Collection< ? extends ResultField> resultFields = model.getResultFields(contextualParameters).values();
112        
113        String versionLabel = (String) jsParameters.get(CONTENT_VERSION_LABEL);
114        
115        saxContents(model, results, resultFields, versionLabel, jsParameters, defaultLocale);
116    }
117    /**
118     * Sax a set of contents
119     * @param model search model
120     * @param results contents
121     * @param resultFields fields to retreive
122     * @param versionLabel version label
123     * @param jsParameters parameters of the search
124     * @param defaultLocale The locale to use for localized values if content's language is null.
125     * @throws SAXException if a error occurred during sax
126     * @throws AmetysRepositoryException if a error occurred
127     * @throws IOException if a error occurred
128     * @throws ProcessingException if a error occurred
129     */
130    protected void saxContents(SearchModel model, SearchResults<Content> results, Collection< ? extends ResultField> resultFields, String versionLabel, Map<String, Object> jsParameters, Locale defaultLocale) throws SAXException, AmetysRepositoryException, IOException, ProcessingException
131    {
132        // Filter the resultFields, using columns if available
133        Collection<ResultField> resultFieldsFiltered = new ArrayList<>();
134        List<String> columns = getColumnsFromParameters(jsParameters).stream().map(Column::getId).collect(Collectors.toList());
135        for (ResultField resultField : resultFields)
136        {
137            if (columns.size() == 0 || columns.contains(resultField.getId()))
138            {
139                resultFieldsFiltered.add(resultField);
140            }
141        }
142
143        contentHandler.startDocument();
144        XMLUtils.startElement(contentHandler, "contents");
145        
146        Iterable<SearchResult<Content>> contents = results.getResults();
147        for (SearchResult<Content> result : contents)
148        {
149            Content content = result.getObject();
150            
151            if (StringUtils.isBlank(versionLabel) || switchToLabel(content, versionLabel))
152            {
153                if (resultFieldsFiltered.size() > 0)
154                {
155                    saxContent(content, resultFieldsFiltered, defaultLocale);
156                }
157                else
158                {
159                    saxContent(content, resultFields, defaultLocale);
160                }
161            }
162        }
163        
164        XMLUtils.endElement(contentHandler, "contents");
165        contentHandler.endDocument();
166    }
167    
168    /**
169     * Get the columns from JS parameters
170     * @param jsParameters The JS parameters
171     * @return the requested columns
172     */
173    @SuppressWarnings("unchecked")
174    protected List<Column> getColumnsFromParameters(Map<String, Object> jsParameters)
175    {
176        Map<String, Object> values = (Map<String, Object>) jsParameters.get("values");
177        
178        if (values != null && values.containsKey("columns"))
179        {
180            Object columnsAsObj = values.get("columns");
181            if (columnsAsObj instanceof String)
182            {
183                return SolrQuerySearchAction.getColumns((String) columnsAsObj);
184            }
185            else
186            {
187                return SolrQuerySearchAction.getColumns((List<String>) columnsAsObj);
188            }
189        }
190        return Collections.EMPTY_LIST;
191    }
192    
193    /**
194     * Switch to the revision corresponding to the specified label.
195     * @param content The content
196     * @param label the label to switch to
197     * @return <code>true</code> if a revision with this label exists and the content was switch, <code>false</code> otherwise.
198     */
199    protected boolean switchToLabel(Content content, String label)
200    {
201        if (content instanceof VersionAwareAmetysObject)
202        {
203            String[] allLabels = ((VersionAwareAmetysObject) content).getAllLabels();
204            if (ArrayUtils.contains(allLabels, label))
205            {
206                ((VersionAwareAmetysObject) content).switchToLabel(label);
207                return true;
208            }
209        }
210        
211        return false;
212    }
213    
214    /**
215     * SAX the result content
216     * @param content the result
217     * @param resultFields the result fields
218     * @param defaultLocale The locale to use for localized values if content's language is null.
219     * @throws SAXException if a error occurred during sax
220     * @throws AmetysRepositoryException if a error occurred
221     * @throws IOException if a error occurred
222     */
223    protected void saxContent(Content content, Collection<? extends ResultField> resultFields, Locale defaultLocale) throws SAXException, AmetysRepositoryException, IOException
224    {
225        Request request = ContextHelper.getRequest(_context);
226        
227        try
228        {
229            request.setAttribute(Content.class.getName(), content);
230            
231            AttributesImpl attrs = new AttributesImpl();
232            attrs.addCDATAAttribute("id", content.getId());
233            attrs.addCDATAAttribute("name", content.getName());
234            attrs.addCDATAAttribute("title", content.getTitle(defaultLocale));
235            if (content.getLanguage() != null)
236            {
237                attrs.addCDATAAttribute("language", content.getLanguage());
238            }
239                
240            XMLUtils.startElement(contentHandler, "content", attrs);
241            _contentRSH.saxResultFields(contentHandler, content, resultFields, defaultLocale);
242            XMLUtils.endElement(contentHandler, "content");
243        }
244        finally
245        {
246            request.setAttribute(Content.class.getName(), null);
247        }
248    }
249}