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