001/*
002 *  Copyright 2010 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.web.frontoffice;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.cocoon.environment.ObjectModelHelper;
028import org.apache.cocoon.environment.Request;
029import org.apache.cocoon.xml.AttributesImpl;
030import org.apache.cocoon.xml.XMLUtils;
031import org.apache.commons.lang3.ArrayUtils;
032import org.apache.commons.lang3.StringUtils;
033import org.apache.solr.client.solrj.util.ClientUtils;
034import org.xml.sax.SAXException;
035
036import org.ametys.cms.content.indexing.solr.SolrFieldNames;
037import org.ametys.cms.search.SearchResult;
038import org.ametys.cms.search.SearchResults;
039import org.ametys.cms.search.Sort;
040import org.ametys.cms.search.Sort.Order;
041import org.ametys.cms.search.query.AndQuery;
042import org.ametys.cms.search.query.DublinCoreQuery;
043import org.ametys.cms.search.query.FullTextQuery;
044import org.ametys.cms.search.query.OrQuery;
045import org.ametys.cms.search.query.Query;
046import org.ametys.plugins.explorer.resources.Resource;
047import org.ametys.plugins.repository.AmetysObject;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.web.indexing.solr.SolrWebFieldNames;
050import org.ametys.web.repository.page.Page;
051import org.ametys.web.repository.site.Site;
052import org.ametys.web.search.query.PageQuery;
053import org.ametys.web.search.query.SiteQuery;
054import org.ametys.web.search.query.SitemapQuery;
055
056/**
057 * Generates the results of a search performed on front office
058 */
059public class SearchResourcesGenerator extends AbstractSearchGenerator
060{
061    /** MIME types for images */
062    protected static final String[] IMAGE_MIMETYPE = new String [] {"image/gif", "image/png", "image/jpeg"};
063    /** MIME types for videos */
064    protected static final String[] VIDEO_MIMETYPE = new String [] {"application/x-shockwave-flash", "video/mpeg,video/mp4", "video/quicktime", "video/x-flv", "video/msvideo", "video/x-msvideo", "video/x-ms-wmv"};
065    /** MIME types for audio files */
066    protected static final String[] AUDIO_MIMETYPE = new String [] {"audio/mpeg", "audio/x-mpeg", "audio/x-ms-wma", "audio/vnd.rn-realaudio", "audio/x-wav"};
067    /** MIME types for pdf */
068    protected static final String[] PDF_MIMETYPE = new String [] {"application/pdf"};
069    /** MIME types for text documents */
070    protected static final String[] TEXT_MIMETYPE = new String [] {"application/msword", "vnd.oasis.opendocument.text", "application/rtf,text/rtf"};
071    /** MIME types for presentation */
072    protected static final String[] PRES_MIMETYPE = new String [] {"application/vnd.ms-powerpoint", "application/vnd.oasis.opendocument.presentation"};
073    /** MIME types for spreadsheets */
074    protected static final String[] SPREADSHEET_MIMETYPE = new String [] {"vnd.oasis.opendocument.spreadsheet", "application/vnd.ms-excel"};
075    
076    @Override
077    protected Query getQuery(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException
078    {
079        List<Query> queries = new ArrayList<>();
080        
081        _addTextFieldQuery(queries, language, request);
082        _addPagesQuery(queries, request);
083        _addCreatorFieldQuery(queries, request);
084        _addFormatQuery(queries, request);
085        _addPublisherQuery(queries, request);
086        _addRightsQuery(queries, request);
087        
088        return new AndQuery(queries);
089    }
090    
091    @Override
092    protected Collection<Query> getFilterQueries(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException
093    {
094        Query siteQuery = new SiteQuery(siteNames);
095        Query langQuery = new SitemapQuery(language);
096        
097        return Arrays.asList(siteQuery, langQuery);
098    }
099    
100    @Override
101    protected Collection<String> getDocumentTypes(Request request)
102    {
103        return Arrays.asList(TYPE_PAGE_RESOURCE);
104    }
105    
106    @Override
107    protected Collection<String> getFields()
108    {
109        return Arrays.asList("DCTitle", "DCDescription", "DCSubject", "full");
110    }
111    
112    @Override
113    protected Map<String, Criterion> getCriteria(Request request, String siteName, String lang)
114    {
115        return Collections.emptyMap();
116    }
117    
118    @Override
119    protected void saxHits(SearchResults<AmetysObject> results, int start, int maxResults) throws SAXException, IOException
120    {
121        long limit = Math.min(start + maxResults, results.getResults().getSize());
122        float maxScore = results.getMaxScore();
123        
124        Iterator<SearchResult<AmetysObject>> it = results.getResults().iterator();
125        for (int i = start; i < limit; i++)
126        {
127            SearchResult<AmetysObject> searchResult = it.next();
128            float score = searchResult.getScore();
129            AmetysObject ametysObject = searchResult.getObject();
130            if (ametysObject instanceof Resource)
131            {
132                saxResourceHit(score, maxScore, (Resource) ametysObject);
133            }
134        }
135    }
136    
137    @Override
138    protected Sort getSortField(Request request)
139    {
140        if (request.getParameter("sort-by-title-for-sorting") != null)
141        {
142            return new Sort(SolrFieldNames.TITLE_SORT, Order.ASC);
143        }
144        else if (request.getParameter("sort-by-date") != null)
145        {
146            return new Sort(SolrFieldNames.DATE_FOR_SORTING, Order.ASC);
147        }
148        
149        return new Sort("score", Order.DESC);
150    }
151    
152    @Override
153    protected List<Sort> getPrimarySortFields(Request request)
154    {
155        return Collections.emptyList();
156    }
157    
158    @Override
159    protected void saxFormFields(Request request, SearchResults<AmetysObject> searchResults, String siteName, String lang) throws SAXException
160    {
161        XMLUtils.createElement(contentHandler, "textfield");
162        
163        if (parameters.getParameterAsBoolean("search-by-creator", false))
164        {
165            XMLUtils.createElement(contentHandler, "creator");
166        }
167        
168        if (parameters.getParameterAsBoolean("search-by-format", false))
169        {
170            _saxFormatCriteria();
171        }
172        
173        if (parameters.getParameterAsBoolean("search-by-publisher", false))
174        {
175            _saxPublisherCriteria();
176        }
177        
178        if (parameters.getParameterAsBoolean("search-by-rights", false))
179        {
180            _saxRightsCriteria();
181        }
182        
183        _saxSitemapCriteria();
184        
185        boolean multisite = parameters.getParameterAsBoolean("search-multisite", false);
186        if (multisite)
187        {
188            XMLUtils.createElement(contentHandler, "multisite");
189            
190            XMLUtils.startElement(contentHandler, "sites");
191            Collection<String> allSites = _siteManager.getSiteNames();
192            for (String name : allSites)
193            {
194                Site site = _siteManager.getSite(name);
195                AttributesImpl attr = new AttributesImpl();
196                attr.addCDATAAttribute("name", name);
197                if (name.equals(siteName))
198                {
199                    attr.addCDATAAttribute("current", "true");
200                }
201                XMLUtils.createElement(contentHandler, "site", attr, site.getTitle());
202            }
203            XMLUtils.endElement(contentHandler, "sites");
204        }
205    }
206    
207    private void _saxSitemapCriteria()  throws SAXException
208    {
209        @SuppressWarnings("unchecked")
210        Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
211        if (parentContext == null)
212        {
213            parentContext = Collections.emptyMap();
214        }
215        
216        String[] pages = (String[]) parentContext.get("search-by-pages");
217        if (ArrayUtils.isNotEmpty(pages))
218        {
219            XMLUtils.startElement(contentHandler, "pages");
220            
221            for (String pageID : pages)
222            {
223                Page page = _resolver.resolveById(pageID);
224                AttributesImpl attr = new AttributesImpl();
225                attr.addCDATAAttribute("id", pageID);
226                attr.addCDATAAttribute("path", page.getSitemap().getName() + "/" + page.getPathInSitemap());
227                attr.addCDATAAttribute("title", page.getTitle());
228                attr.addCDATAAttribute("long-title", page.getLongTitle());
229                XMLUtils.createElement(contentHandler, "page", attr);
230            }
231            
232            XMLUtils.endElement(contentHandler, "pages");
233        }
234    }
235    
236    @Override
237    protected void saxFormValues(Request request, SearchResults<AmetysObject> searchResults, int start, int offset) throws SAXException
238    {
239        _saxTextField(request);
240        _saxPages(request);
241        _saxCreatorField (request);
242        _saxFormats(request);
243        _saxPublisher(request);
244        _saxRights(request);
245        _saxMultisite(request);
246    }
247    
248    private void _saxTextField(Request request) throws SAXException
249    {
250        String textfield = request.getParameter("textfield");
251        XMLUtils.createElement(contentHandler, "textfield", textfield != null ? textfield : "");
252    }
253    
254    private void _saxPages(Request request) throws SAXException
255    {
256        String[] pages = request.getParameterValues("pages");
257        if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals("")))
258        {
259            for (String path : pages)
260            {
261                XMLUtils.createElement(contentHandler, "page", path);
262            }
263        }
264    }
265    
266    private void _saxCreatorField(Request request) throws SAXException
267    {
268        String creator = request.getParameter("creator");
269        if (!StringUtils.isEmpty(creator))
270        {
271            XMLUtils.createElement(contentHandler, "creator", creator != null ? creator : "");
272        }
273    }
274    
275    private void _saxFormats(Request request) throws SAXException
276    {
277        String[] formats = request.getParameterValues("dc_format");
278        if (formats != null && formats.length > 0 && !(formats.length == 1 && formats[0].equals("")))
279        {
280            for (String format : formats)
281            {
282                XMLUtils.createElement(contentHandler, "format", format);
283            }
284        }
285    }
286    
287    private void _saxPublisher (Request request) throws SAXException
288    {
289        String[] publishers = request.getParameterValues("dc_publisher");
290        if (publishers != null && publishers.length > 0 && !(publishers.length == 1 && publishers[0].equals("")))
291        {
292            for (String publisher : publishers)
293            {
294                XMLUtils.createElement(contentHandler, "publisher", publisher);
295            }
296        }
297    }
298    
299    private void _saxRights (Request request) throws SAXException
300    {
301        String[] rights = request.getParameterValues("dc_rights");
302        if (rights != null && rights.length > 0 && !(rights.length == 1 && rights[0].equals("")))
303        {
304            for (String right : rights)
305            {
306                XMLUtils.createElement(contentHandler, "rights", right);
307            }
308        }
309    }
310    
311    private void _saxMultisite(Request request) throws SAXException
312    {
313        boolean multisite = request.getParameter("multisite") != null;
314        if (multisite)
315        {
316            XMLUtils.createElement(contentHandler, "multisite");
317            
318            String[] sites = request.getParameterValues("sites");
319            if (sites != null && sites.length > 0 && !(sites.length == 1 && sites[0].equals("")))
320            {
321                for (String site : sites)
322                {
323                    XMLUtils.createElement(contentHandler, "site", site);
324                }
325            }
326        }
327    }
328    
329    private void _saxFormatCriteria () throws SAXException
330    {
331        XMLUtils.startElement(contentHandler, "formats");
332        
333        AttributesImpl attr = new AttributesImpl();
334        attr.addCDATAAttribute("value", StringUtils.join(PDF_MIMETYPE, ","));
335        XMLUtils.startElement(contentHandler, "format", attr);
336        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_PDF").toSAX(contentHandler);
337        XMLUtils.endElement(contentHandler, "format");
338        
339        attr = new AttributesImpl();
340        attr.addCDATAAttribute("value", StringUtils.join(IMAGE_MIMETYPE, ","));
341        XMLUtils.startElement(contentHandler, "format", attr);
342        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_IMAGE").toSAX(contentHandler);
343        XMLUtils.endElement(contentHandler, "format");
344        
345        attr = new AttributesImpl();
346        attr.addCDATAAttribute("value", StringUtils.join(TEXT_MIMETYPE, ","));
347        XMLUtils.startElement(contentHandler, "format", attr);
348        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_TEXT").toSAX(contentHandler);
349        XMLUtils.endElement(contentHandler, "format");
350        
351        attr = new AttributesImpl();
352        attr.addCDATAAttribute("value", StringUtils.join(PRES_MIMETYPE, ","));
353        XMLUtils.startElement(contentHandler, "format", attr);
354        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_PRES").toSAX(contentHandler);
355        XMLUtils.endElement(contentHandler, "format");
356        
357        attr = new AttributesImpl();
358        attr.addCDATAAttribute("value", StringUtils.join(SPREADSHEET_MIMETYPE, ","));
359        XMLUtils.startElement(contentHandler, "format", attr);
360        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_SPREADSHEET").toSAX(contentHandler);
361        XMLUtils.endElement(contentHandler, "format");
362        
363        attr = new AttributesImpl();
364        attr.addCDATAAttribute("value", StringUtils.join(VIDEO_MIMETYPE, ","));
365        XMLUtils.startElement(contentHandler, "format", attr);
366        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_VIDEO").toSAX(contentHandler);
367        XMLUtils.endElement(contentHandler, "format");
368        
369        attr = new AttributesImpl();
370        attr.addCDATAAttribute("value", StringUtils.join(AUDIO_MIMETYPE, ","));
371        XMLUtils.startElement(contentHandler, "format", attr);
372        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_AUDIO").toSAX(contentHandler);
373        XMLUtils.endElement(contentHandler, "format");
374        
375        XMLUtils.endElement(contentHandler, "formats");
376    }
377    
378    private void _saxPublisherCriteria () throws SAXException
379    {
380        Map<String, I18nizableText> entries = _dcProvider.getEntries("dc_publisher");
381        if (entries != null)
382        {
383            XMLUtils.startElement(contentHandler, "publishers");
384            
385            for (String value : entries.keySet())
386            {
387                AttributesImpl attr = new AttributesImpl();
388                attr.addCDATAAttribute("value", value);
389                XMLUtils.startElement(contentHandler, "publisher", attr);
390                entries.get(value).toSAX(contentHandler);
391                XMLUtils.endElement(contentHandler, "publisher");
392            }
393            
394            XMLUtils.endElement(contentHandler, "publishers");
395        }
396        
397    }
398    
399    private void _saxRightsCriteria () throws SAXException
400    {
401        Map<String, I18nizableText> entries = _dcProvider.getEntries("dc_rights");
402        if (entries != null)
403        {
404            XMLUtils.startElement(contentHandler, "rights");
405            
406            for (String value : entries.keySet())
407            {
408                AttributesImpl attr = new AttributesImpl();
409                attr.addCDATAAttribute("value", value);
410                XMLUtils.startElement(contentHandler, "rights", attr);
411                entries.get(value).toSAX(contentHandler);
412                XMLUtils.endElement(contentHandler, "rights");
413            }
414            
415            XMLUtils.endElement(contentHandler, "rights");
416        }
417    }
418    
419    private void _addTextFieldQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException
420    {
421        String text = request.getParameter("textfield");
422        
423        if (StringUtils.isNotBlank(text))
424        {
425            queries.add(new FullTextQuery(text, language));
426        }
427    }
428    
429    private void _addPagesQuery(Collection<Query> queries, Request request)
430    {
431        List<Query> pageQueries = new ArrayList<>();
432        
433        String[] pages = request.getParameterValues("pages");
434        if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals("")))
435        {
436            for (String pageIds : pages)
437            {
438                for (String pageId : StringUtils.split(pageIds, ","))
439                {
440                    pageQueries.add(() -> "{!join from=\"" + SolrFieldNames.ID + "\" to=\"" + SolrWebFieldNames.PAGE_ID + "\" v=\"" + new PageQuery(pageId, true).build().replace("\"", "\\\"") + "\"}");
441                }
442            }
443        }
444        
445        queries.add(new OrQuery(pageQueries));
446    }
447    
448    private void _addCreatorFieldQuery(Collection<Query> queries, Request request)
449    {
450        String creator = request.getParameter("dc_creator");
451        
452        if (StringUtils.isNotEmpty(creator))
453        {
454            String escapedCreator = ClientUtils.escapeQueryChars(creator);
455            queries.add(new DublinCoreQuery("creator", escapedCreator));
456        }
457    }
458    
459    private void _addFormatQuery(Collection<Query> queries, Request request)
460    {
461        String[] formats = request.getParameterValues("dc_format");
462        if (formats != null && formats.length > 0 && !(formats.length == 1 && formats[0].equals("")))
463        {
464            List<Query> formatQueries = new ArrayList<>();
465            
466            for (String formatList : formats)
467            {
468                for (String value : StringUtils.split(formatList, ','))
469                {
470                    String escapedFormat = ClientUtils.escapeQueryChars(value);
471                    formatQueries.add(new DublinCoreQuery("format", escapedFormat));
472                }
473            }
474            
475            queries.add(new OrQuery(formatQueries));
476        }
477    }
478    
479    private void _addPublisherQuery(Collection<Query> queries, Request request)
480    {
481        String[] publishers = request.getParameterValues("dc_publisher");
482        if (publishers != null && publishers.length > 0 && !(publishers.length == 1 && publishers[0].equals("")))
483        {
484            List<Query> pubQueries = new ArrayList<>();
485            
486            for (String pubList : publishers)
487            {
488                for (String value : StringUtils.split(pubList, ','))
489                {
490                    String escapedPub = ClientUtils.escapeQueryChars(value);
491                    pubQueries.add(new DublinCoreQuery("publisher", escapedPub));
492                }
493            }
494            
495            queries.add(new OrQuery(pubQueries));
496        }
497    }
498    
499    private void _addRightsQuery(Collection<Query> queries, Request request)
500    {
501        String[] rights = request.getParameterValues("dc_rights");
502        if (rights != null && rights.length > 0 && !(rights.length == 1 && rights[0].equals("")))
503        {
504            List<Query> rightQueries = new ArrayList<>();
505            
506            for (String rightList : rights)
507            {
508                for (String value : StringUtils.split(rightList, ','))
509                {
510                    String escapedRight = ClientUtils.escapeQueryChars(value);
511                    rightQueries.add(new DublinCoreQuery("rights", escapedRight));
512                }
513            }
514            
515            queries.add(new OrQuery(rightQueries));
516        }
517    }
518}