001/*
002 *  Copyright 2018 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.search.metamodel.impl;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.Optional;
025
026import org.apache.avalon.framework.activity.Disposable;
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.configuration.Configurable;
029import org.apache.avalon.framework.configuration.Configuration;
030import org.apache.avalon.framework.configuration.ConfigurationException;
031import org.apache.avalon.framework.configuration.DefaultConfiguration;
032import org.apache.avalon.framework.context.Context;
033import org.apache.avalon.framework.context.ContextException;
034import org.apache.avalon.framework.context.Contextualizable;
035import org.apache.avalon.framework.service.ServiceException;
036import org.apache.avalon.framework.service.ServiceManager;
037import org.apache.avalon.framework.service.Serviceable;
038
039import org.ametys.cms.content.indexing.solr.SolrFieldNames;
040import org.ametys.cms.contenttype.MetadataType;
041import org.ametys.cms.languages.LanguageEnumerator;
042import org.ametys.cms.search.advanced.AbstractTreeNode;
043import org.ametys.cms.search.query.JoinQuery;
044import org.ametys.cms.search.query.OrQuery;
045import org.ametys.cms.search.query.Query;
046import org.ametys.cms.search.query.QuerySyntaxException;
047import org.ametys.cms.search.query.join.JoinKey;
048import org.ametys.plugins.explorer.dublincore.DublinCoreMetadataProvider;
049import org.ametys.plugins.explorer.resources.Resource;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.runtime.parameter.Enumerator;
052import org.ametys.runtime.plugin.component.AbstractLogEnabled;
053import org.ametys.runtime.plugin.component.PluginAware;
054import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
055import org.ametys.web.frontoffice.search.instance.model.FOSearchCriterion;
056import org.ametys.web.frontoffice.search.metamodel.AdditionalParameterValueMap;
057import org.ametys.web.frontoffice.search.metamodel.Returnable;
058import org.ametys.web.frontoffice.search.metamodel.ReturnableExtensionPoint;
059import org.ametys.web.frontoffice.search.metamodel.SearchCriterionDefinition;
060import org.ametys.web.frontoffice.search.metamodel.Searchable;
061import org.ametys.web.frontoffice.search.requesttime.impl.SearchComponentHelper;
062import org.ametys.web.indexing.solr.SolrWebFieldNames;
063import org.ametys.web.search.query.PageContentQuery;
064
065/**
066 * {@link Searchable} for {@link Resource}s
067 */
068public class ResourceSearchable extends AbstractLogEnabled implements Searchable, Serviceable, PluginAware, Contextualizable, Initializable, Disposable, Configurable
069{
070    /** The prefix for the ids of criterion definitions */
071    protected static final String __CRITERION_DEFINITIONS_PREFIX_ID = "ResourceSearchable$";
072    
073    /** The label */
074    protected I18nizableText _label;
075    /** The criteria position */
076    protected int _criteriaPosition;
077    
078    private ServiceManager _manager;
079    private Returnable _resourceReturnable;
080    private Returnable _pageReturnable;
081    private Returnable _contentReturnable;
082    private String _pluginName;
083    private Context _context;
084
085    private DublinCoreMetadataProvider _dcProvider;
086    private ThreadSafeComponentManager<Enumerator> _enumeratorManager;
087    private LanguageEnumerator _sitemapEnumerator;
088    private SearchComponentHelper _searchComponentHelper;
089    
090    @Override
091    public void service(ServiceManager manager) throws ServiceException
092    {
093        ReturnableExtensionPoint resultTypeEP = (ReturnableExtensionPoint) manager.lookup(ReturnableExtensionPoint.ROLE);
094        _resourceReturnable = resultTypeEP.getExtension(ResourceReturnable.ROLE);
095        _pageReturnable = resultTypeEP.getExtension(PageReturnable.ROLE);
096        _contentReturnable = resultTypeEP.getExtension(ContentReturnable.ROLE);
097        
098        _manager = manager;
099        _dcProvider = (DublinCoreMetadataProvider) manager.lookup(DublinCoreMetadataProvider.ROLE);
100        _searchComponentHelper = (SearchComponentHelper) manager.lookup(SearchComponentHelper.ROLE);
101    }
102    
103    @Override
104    public void configure(Configuration configuration) throws ConfigurationException
105    {
106        _label = I18nizableText.parseI18nizableText(configuration.getChild("label"), "plugin.web");
107        _criteriaPosition = configuration.getChild("criteriaPosition").getValueAsInteger();
108    }
109    
110    @Override
111    public void setPluginInfo(String pluginName, String featureName, String id)
112    {
113        _pluginName = pluginName;
114    }
115    
116    @Override
117    public void contextualize(Context context) throws ContextException
118    {
119        _context = context;
120    }
121    
122    @Override
123    public void initialize() throws Exception
124    {
125        _enumeratorManager = new ThreadSafeComponentManager<>();
126        _enumeratorManager.setLogger(getLogger());
127        _enumeratorManager.contextualize(_context);
128        _enumeratorManager.service(_manager);
129        
130        String sitemapRole = "sitemap";
131        _enumeratorManager.addComponent(_pluginName, null, sitemapRole, LanguageEnumerator.class, new DefaultConfiguration("enumerator"));
132        _enumeratorManager.initialize();
133        _sitemapEnumerator = (LanguageEnumerator) _enumeratorManager.lookup(sitemapRole);
134        Objects.nonNull(_sitemapEnumerator);
135    }
136    
137    @Override
138    public void dispose()
139    {
140        _enumeratorManager.dispose();
141        _enumeratorManager = null;
142    }
143    
144    @Override
145    public I18nizableText getLabel()
146    {
147        return _label;
148    }
149
150    @Override
151    public Collection<SearchCriterionDefinition> getCriteria(AdditionalParameterValueMap additionalParameterValues)
152    {
153        Collection<SearchCriterionDefinition> criteria = new ArrayList<>();
154        String catalogue = "plugin.web";
155        
156        // Dublin-Core criteria
157        criteria.add(new ResourceFormatSearchCriterionDefinition(__CRITERION_DEFINITIONS_PREFIX_ID + "format", _pluginName, new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_FORMAT"), Optional.of(this)));
158        criteria.add(new DublinCoreSearchCriterionDefinition(
159                __CRITERION_DEFINITIONS_PREFIX_ID + "creator",
160                _pluginName,
161                new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_CREATOR"),
162                MetadataType.STRING,
163                Optional.empty(),
164                Optional.of(this),
165                "creator"));
166        criteria.add(new DublinCoreSearchCriterionDefinition(
167                __CRITERION_DEFINITIONS_PREFIX_ID + "publisher",
168                _pluginName,
169                new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_PUBLISHER"),
170                MetadataType.STRING,
171                Optional.ofNullable(_dcProvider.getEntries("dc_publisher")),
172                Optional.of(this),
173                "publisher"));
174        criteria.add(new DublinCoreSearchCriterionDefinition(
175                __CRITERION_DEFINITIONS_PREFIX_ID + "rights",
176                _pluginName,
177                new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_RIGHTS"),
178                MetadataType.STRING,
179                Optional.ofNullable(_dcProvider.getEntries("dc_rights")),
180                Optional.of(this),
181                "rights"));
182        
183        // Sitemap criteria
184        criteria.add(new SitemapSearchCriterionDefinition(__CRITERION_DEFINITIONS_PREFIX_ID + "sitemap", _pluginName, new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_SITEMAP"), _sitemapEnumerator, Optional.of(this)));
185        
186        return criteria;
187    }
188    
189    @Override
190    public int criteriaPosition()
191    {
192        return _criteriaPosition;
193    }
194
195    @Override
196    public Optional<Query> joinQuery(Query queryOnCriterion, SearchCriterionDefinition criterion, Collection<Returnable> returnables, AdditionalParameterValueMap additionalParameters)
197    {
198        List<Query> queries = new ArrayList<>();
199        
200        boolean isSubQueryNonEmpty = _isSubQueryNonEmpty(queryOnCriterion);
201        
202        if (returnables.contains(_resourceReturnable))
203        {
204            queries.add(queryOnCriterion);
205        }
206        
207        if (returnables.contains(_pageReturnable))
208        {
209            // join query on visible page attachments
210            if (isSubQueryNonEmpty)
211            {
212                queries.add(_queryOnPage(queryOnCriterion));
213                queries.add(_queryOnPageThroughContents(queryOnCriterion));
214            }
215        }
216        
217        if (returnables.contains(_contentReturnable))
218        {
219            // join query on visible content attachments
220            if (isSubQueryNonEmpty)
221            {
222                queries.add(_queryOnContent(queryOnCriterion));
223            }
224        }
225        
226        return queries.size() > 0 ? Optional.of(new OrQuery(queries)) : Optional.empty();
227    }
228    
229    private boolean _isSubQueryNonEmpty(Query queryOnCriterion)
230    {
231        try
232        {
233            return !queryOnCriterion.build().isEmpty();
234        }
235        catch (QuerySyntaxException e)
236        {
237            // Silently ignore, queryOnCriterion will be built later anyway
238        }
239        return false;
240    }
241    
242    private Query _queryOnContent(Query queryOnVisibleContentAttachments)
243    {
244        return new JoinQuery(queryOnVisibleContentAttachments, new JoinKey(SolrFieldNames.CONTENT_VISIBLE_ATTACHMENT_RESOURCE_IDS, SolrFieldNames.RESOURCE_ANCESTOR_AND_SELF_IDS, null));
245    }
246    
247    private Query _queryOnPage(Query queryOnVisiblePageAttachments)
248    {
249        return new JoinQuery(queryOnVisiblePageAttachments, new JoinKey(SolrWebFieldNames.PAGE_VISIBLE_ATTACHMENT_RESOURCE_IDS, SolrFieldNames.RESOURCE_ANCESTOR_AND_SELF_IDS, null)); 
250    }
251    
252    private Query _queryOnPageThroughContents(Query queryOnVisibleContentAttachments)
253    {
254        return new PageContentQuery(_queryOnContent(queryOnVisibleContentAttachments));
255    }
256    
257    @Override
258    public Collection<Returnable> relationsWith()
259    {
260        return Arrays.asList(_resourceReturnable, _pageReturnable, _contentReturnable);
261    }
262    
263    @Override
264    public Query buildQuery(
265            AbstractTreeNode<FOSearchCriterion> criterionTree, 
266            Map<String, Object> userCriteria, 
267            Collection<Returnable> returnables,
268            Collection<Searchable> searchables, 
269            AdditionalParameterValueMap additionalParameters, 
270            String currentLang, 
271            Map<String, Object> contextualParameters)
272    {
273        return _searchComponentHelper.buildQuery(criterionTree, userCriteria, returnables, searchables, additionalParameters, currentLang, null, contextualParameters);
274    }
275}