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 Collection<String> getFields()
125    {
126        return Arrays.asList("DCTitle", "DCDescription", "DCSubject", "full");
127    }
128    
129    @Override
130    protected Map<String, FacetField> getFacets(Request request) throws IllegalArgumentException
131    {
132        return MapUtils.EMPTY_MAP;
133    }
134    
135    @Override
136    protected Set<QueryFacet> getQueryFacets(Request request)
137    {
138        return Collections.EMPTY_SET;
139    }
140    
141    @Override
142    protected Map<String, List<String>> getFacetValues(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException
143    {
144        return MapUtils.EMPTY_MAP;
145    }
146    
147    @Override
148    protected Collection<String> getQueryFacetValues(Request request)
149    {
150        return Collections.EMPTY_SET;
151    }
152    
153    @Override
154    protected void saxHits(SearchResults<AmetysObject> results, int start, int maxResults) throws SAXException, IOException
155    {
156        long limit = Math.min(start + maxResults, results.getResults().getSize());
157        float maxScore = results.getMaxScore();
158        
159        Iterator<SearchResult<AmetysObject>> it = results.getResults().iterator();
160        for (int i = start; i < limit; i++)
161        {
162            SearchResult<AmetysObject> searchResult = it.next();
163            float score = searchResult.getScore();
164            AmetysObject ametysObject = searchResult.getObject();
165            if (ametysObject instanceof Resource)
166            {
167                saxResourceHit(score, maxScore, (Resource) ametysObject);
168            }
169        }
170    }
171    
172    @Override
173    protected Sort getSortField(Request request)
174    {
175        if (request.getParameter("sort-by-title-for-sorting") != null)
176        {
177            return new Sort(SolrFieldNames.TITLE_SORT, Order.ASC);
178        }
179        else if (request.getParameter("sort-by-date") != null)
180        {
181            return new Sort(SolrFieldNames.DATE_FOR_SORTING, Order.ASC);
182        }
183        
184        return new Sort("score", Order.DESC);
185    }
186    
187    @Override
188    protected List<Sort> getPrimarySortFields(Request request)
189    {
190        return Collections.emptyList();
191    }
192    
193    @Override
194    protected void saxFormFields(Request request, String siteName, String lang) throws SAXException
195    {
196        XMLUtils.createElement(contentHandler, "textfield");
197        
198        if (parameters.getParameterAsBoolean("search-by-creator", false))
199        {
200            XMLUtils.createElement(contentHandler, "creator");
201        }
202        
203        if (parameters.getParameterAsBoolean("search-by-format", false))
204        {
205            _saxFormatCriteria();
206        }
207        
208        if (parameters.getParameterAsBoolean("search-by-publisher", false))
209        {
210            _saxPublisherCriteria();
211        }
212        
213        if (parameters.getParameterAsBoolean("search-by-rights", false))
214        {
215            _saxRightsCriteria();
216        }
217        
218        _saxSitemapCriteria();
219        
220        boolean multisite = parameters.getParameterAsBoolean("search-multisite", false);
221        if (multisite)
222        {
223            XMLUtils.createElement(contentHandler, "multisite");
224            
225            XMLUtils.startElement(contentHandler, "sites");
226            Collection<String> allSites = _siteManager.getSiteNames();
227            for (String name : allSites)
228            {
229                Site site = _siteManager.getSite(name);
230                AttributesImpl attr = new AttributesImpl();
231                attr.addCDATAAttribute("name", name);
232                if (name.equals(siteName))
233                {
234                    attr.addCDATAAttribute("current", "true");
235                }
236                XMLUtils.createElement(contentHandler, "site", attr, site.getTitle());
237            }
238            XMLUtils.endElement(contentHandler, "sites");
239        }
240    }
241    
242    private void _saxSitemapCriteria()  throws SAXException
243    {
244        @SuppressWarnings("unchecked")
245        Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
246        if (parentContext == null)
247        {
248            parentContext = Collections.emptyMap();
249        }
250        
251        String[] pages = (String[]) parentContext.get("search-by-pages");
252        if (ArrayUtils.isNotEmpty(pages))
253        {
254            XMLUtils.startElement(contentHandler, "pages");
255            
256            for (String pageID : pages)
257            {
258                Page page = _resolver.resolveById(pageID);
259                AttributesImpl attr = new AttributesImpl();
260                attr.addCDATAAttribute("id", pageID);
261                attr.addCDATAAttribute("path", page.getSitemap().getName() + "/" + page.getPathInSitemap());
262                attr.addCDATAAttribute("title", page.getTitle());
263                attr.addCDATAAttribute("long-title", page.getLongTitle());
264                XMLUtils.createElement(contentHandler, "page", attr);
265            }
266            
267            XMLUtils.endElement(contentHandler, "pages");
268        }
269    }
270    
271    @Override
272    protected void saxFormValues(Request request, int start, int offset) throws SAXException
273    {
274        _saxTextField(request);
275        _saxPages(request);
276        _saxCreatorField (request);
277        _saxFormats(request);
278        _saxPublisher(request);
279        _saxRights(request);
280        _saxMultisite(request);
281    }
282    
283    private void _saxTextField(Request request) throws SAXException
284    {
285        String textfield = request.getParameter("textfield");
286        XMLUtils.createElement(contentHandler, "textfield", textfield != null ? textfield : "");
287    }
288    
289    private void _saxPages(Request request) throws SAXException
290    {
291        String[] pages = request.getParameterValues("pages");
292        if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals("")))
293        {
294            for (String path : pages)
295            {
296                XMLUtils.createElement(contentHandler, "page", path);
297            }
298        }
299    }
300    
301    private void _saxCreatorField(Request request) throws SAXException
302    {
303        String creator = request.getParameter("creator");
304        if (!StringUtils.isEmpty(creator))
305        {
306            XMLUtils.createElement(contentHandler, "creator", creator != null ? creator : "");
307        }
308    }
309    
310    private void _saxFormats(Request request) throws SAXException
311    {
312        String[] formats = request.getParameterValues("dc_format");
313        if (formats != null && formats.length > 0 && !(formats.length == 1 && formats[0].equals("")))
314        {
315            for (String format : formats)
316            {
317                XMLUtils.createElement(contentHandler, "format", format);
318            }
319        }
320    }
321    
322    private void _saxPublisher (Request request) throws SAXException
323    {
324        String[] publishers = request.getParameterValues("dc_publisher");
325        if (publishers != null && publishers.length > 0 && !(publishers.length == 1 && publishers[0].equals("")))
326        {
327            for (String publisher : publishers)
328            {
329                XMLUtils.createElement(contentHandler, "publisher", publisher);
330            }
331        }
332    }
333    
334    private void _saxRights (Request request) throws SAXException
335    {
336        String[] rights = request.getParameterValues("dc_rights");
337        if (rights != null && rights.length > 0 && !(rights.length == 1 && rights[0].equals("")))
338        {
339            for (String right : rights)
340            {
341                XMLUtils.createElement(contentHandler, "rights", right);
342            }
343        }
344    }
345    
346    private void _saxMultisite(Request request) throws SAXException
347    {
348        boolean multisite = request.getParameter("multisite") != null;
349        if (multisite)
350        {
351            XMLUtils.createElement(contentHandler, "multisite");
352            
353            String[] sites = request.getParameterValues("sites");
354            if (sites != null && sites.length > 0 && !(sites.length == 1 && sites[0].equals("")))
355            {
356                for (String site : sites)
357                {
358                    XMLUtils.createElement(contentHandler, "site", site);
359                }
360            }
361        }
362    }
363    
364    private void _saxFormatCriteria () throws SAXException
365    {
366        XMLUtils.startElement(contentHandler, "formats");
367        
368        AttributesImpl attr = new AttributesImpl();
369        attr.addCDATAAttribute("value", StringUtils.join(PDF_MIMETYPE, ","));
370        XMLUtils.startElement(contentHandler, "format", attr);
371        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_PDF").toSAX(contentHandler);
372        XMLUtils.endElement(contentHandler, "format");
373        
374        attr = new AttributesImpl();
375        attr.addCDATAAttribute("value", StringUtils.join(IMAGE_MIMETYPE, ","));
376        XMLUtils.startElement(contentHandler, "format", attr);
377        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_IMAGE").toSAX(contentHandler);
378        XMLUtils.endElement(contentHandler, "format");
379        
380        attr = new AttributesImpl();
381        attr.addCDATAAttribute("value", StringUtils.join(TEXT_MIMETYPE, ","));
382        XMLUtils.startElement(contentHandler, "format", attr);
383        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_TEXT").toSAX(contentHandler);
384        XMLUtils.endElement(contentHandler, "format");
385        
386        attr = new AttributesImpl();
387        attr.addCDATAAttribute("value", StringUtils.join(PRES_MIMETYPE, ","));
388        XMLUtils.startElement(contentHandler, "format", attr);
389        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_PRES").toSAX(contentHandler);
390        XMLUtils.endElement(contentHandler, "format");
391        
392        attr = new AttributesImpl();
393        attr.addCDATAAttribute("value", StringUtils.join(SPREADSHEET_MIMETYPE, ","));
394        XMLUtils.startElement(contentHandler, "format", attr);
395        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_SPREADSHEET").toSAX(contentHandler);
396        XMLUtils.endElement(contentHandler, "format");
397        
398        attr = new AttributesImpl();
399        attr.addCDATAAttribute("value", StringUtils.join(VIDEO_MIMETYPE, ","));
400        XMLUtils.startElement(contentHandler, "format", attr);
401        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_VIDEO").toSAX(contentHandler);
402        XMLUtils.endElement(contentHandler, "format");
403        
404        attr = new AttributesImpl();
405        attr.addCDATAAttribute("value", StringUtils.join(AUDIO_MIMETYPE, ","));
406        XMLUtils.startElement(contentHandler, "format", attr);
407        new I18nizableText("plugin.web", "PLUGINS_WEB_DOCUMENT_FORMAT_AUDIO").toSAX(contentHandler);
408        XMLUtils.endElement(contentHandler, "format");
409        
410        XMLUtils.endElement(contentHandler, "formats");
411    }
412    
413    private void _saxPublisherCriteria () throws SAXException
414    {
415        Map<String, I18nizableText> entries = _dcProvider.getEntries("dc_publisher");
416        if (entries != null)
417        {
418            XMLUtils.startElement(contentHandler, "publishers");
419            
420            for (String value : entries.keySet())
421            {
422                AttributesImpl attr = new AttributesImpl();
423                attr.addCDATAAttribute("value", value);
424                XMLUtils.startElement(contentHandler, "publisher", attr);
425                entries.get(value).toSAX(contentHandler);
426                XMLUtils.endElement(contentHandler, "publisher");
427            }
428            
429            XMLUtils.endElement(contentHandler, "publishers");
430        }
431        
432    }
433    
434    private void _saxRightsCriteria () throws SAXException
435    {
436        Map<String, I18nizableText> entries = _dcProvider.getEntries("dc_rights");
437        if (entries != null)
438        {
439            XMLUtils.startElement(contentHandler, "rights");
440            
441            for (String value : entries.keySet())
442            {
443                AttributesImpl attr = new AttributesImpl();
444                attr.addCDATAAttribute("value", value);
445                XMLUtils.startElement(contentHandler, "rights", attr);
446                entries.get(value).toSAX(contentHandler);
447                XMLUtils.endElement(contentHandler, "rights");
448            }
449            
450            XMLUtils.endElement(contentHandler, "rights");
451        }
452    }
453    
454    private void _addTextFieldQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException
455    {
456        String text = request.getParameter("textfield");
457        
458        if (StringUtils.isNotBlank(text))
459        {
460            queries.add(new FullTextQuery(text, language));
461        }
462    }
463    
464    private void _addPagesQuery(Collection<Query> queries, Request request)
465    {
466        List<Query> pageQueries = new ArrayList<>();
467        
468        String[] pages = request.getParameterValues("pages");
469        if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals("")))
470        {
471            for (String pageIds : pages)
472            {
473                for (String pageId : StringUtils.split(pageIds, ","))
474                {
475                    pageQueries.add(new JoinQuery(new PageQuery(pageId, true), SolrWebFieldNames.ATTACHMENT_PAGE_ID));
476                }
477            }
478        }
479        
480        queries.add(new OrQuery(pageQueries));
481    }
482    
483    private void _addCreatorFieldQuery(Collection<Query> queries, Request request)
484    {
485        String creator = request.getParameter("dc_creator");
486        
487        if (StringUtils.isNotEmpty(creator))
488        {
489            String escapedCreator = ClientUtils.escapeQueryChars(creator);
490            queries.add(new DublinCoreQuery("creator", escapedCreator));
491        }
492    }
493    
494    private void _addFormatQuery(Collection<Query> queries, Request request)
495    {
496        String[] formats = request.getParameterValues("dc_format");
497        if (formats != null && formats.length > 0 && !(formats.length == 1 && formats[0].equals("")))
498        {
499            List<Query> formatQueries = new ArrayList<>();
500            
501            for (String formatList : formats)
502            {
503                for (String value : StringUtils.split(formatList, ','))
504                {
505                    String escapedFormat = ClientUtils.escapeQueryChars(value);
506                    formatQueries.add(new DublinCoreQuery("format", escapedFormat));
507                }
508            }
509            
510            queries.add(new OrQuery(formatQueries));
511        }
512    }
513    
514    private void _addPublisherQuery(Collection<Query> queries, Request request)
515    {
516        String[] publishers = request.getParameterValues("dc_publisher");
517        if (publishers != null && publishers.length > 0 && !(publishers.length == 1 && publishers[0].equals("")))
518        {
519            List<Query> pubQueries = new ArrayList<>();
520            
521            for (String pubList : publishers)
522            {
523                for (String value : StringUtils.split(pubList, ','))
524                {
525                    String escapedPub = ClientUtils.escapeQueryChars(value);
526                    pubQueries.add(new DublinCoreQuery("publisher", escapedPub));
527                }
528            }
529            
530            queries.add(new OrQuery(pubQueries));
531        }
532    }
533    
534    private void _addRightsQuery(Collection<Query> queries, Request request)
535    {
536        String[] rights = request.getParameterValues("dc_rights");
537        if (rights != null && rights.length > 0 && !(rights.length == 1 && rights[0].equals("")))
538        {
539            List<Query> rightQueries = new ArrayList<>();
540            
541            for (String rightList : rights)
542            {
543                for (String value : StringUtils.split(rightList, ','))
544                {
545                    String escapedRight = ClientUtils.escapeQueryChars(value);
546                    rightQueries.add(new DublinCoreQuery("rights", escapedRight));
547                }
548            }
549            
550            queries.add(new OrQuery(rightQueries));
551        }
552    }
553}