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