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.languages.LanguageEnumerator; 041import org.ametys.cms.search.advanced.AbstractTreeNode; 042import org.ametys.cms.search.query.JoinQuery; 043import org.ametys.cms.search.query.OrQuery; 044import org.ametys.cms.search.query.Query; 045import org.ametys.cms.search.query.QuerySyntaxException; 046import org.ametys.cms.search.query.join.JoinKey; 047import org.ametys.plugins.explorer.dublincore.DublinCoreMetadataProvider; 048import org.ametys.plugins.explorer.resources.Resource; 049import org.ametys.runtime.i18n.I18nizableText; 050import org.ametys.runtime.model.Enumerator; 051import org.ametys.runtime.model.type.ModelItemTypeConstants; 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.SearchServiceCriterion; 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.SearchServiceCriterionDefinition; 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<SearchServiceCriterionDefinition> getCriteria(AdditionalParameterValueMap additionalParameterValues) 152 { 153 Collection<SearchServiceCriterionDefinition> criteria = new ArrayList<>(); 154 String catalogue = "plugin.web"; 155 156 // Dublin-Core criteria 157 SearchServiceCriterionDefinition formatCriterion = new ResourceFormatCriterionDefinition(); 158 formatCriterion.setName(__CRITERION_DEFINITIONS_PREFIX_ID + "format"); 159 formatCriterion.setPluginName(_pluginName); 160 formatCriterion.setLabel(new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_FORMAT")); 161 formatCriterion.setSearchable(this); 162 criteria.add(formatCriterion); 163 164 SearchServiceCriterionDefinition creatorCriterion = new DublinCoreCriterionDefinition<>("creator", ModelItemTypeConstants.STRING_TYPE_ID, Optional.empty()); 165 creatorCriterion.setName(__CRITERION_DEFINITIONS_PREFIX_ID + "creator"); 166 creatorCriterion.setPluginName(_pluginName); 167 creatorCriterion.setLabel(new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_CREATOR")); 168 creatorCriterion.setSearchable(this); 169 criteria.add(creatorCriterion); 170 171 SearchServiceCriterionDefinition publisherCriterion = new DublinCoreCriterionDefinition<>("publisher", ModelItemTypeConstants.STRING_TYPE_ID, Optional.ofNullable(_dcProvider.getEntries("dc_publisher"))); 172 publisherCriterion.setName(__CRITERION_DEFINITIONS_PREFIX_ID + "publisher"); 173 publisherCriterion.setPluginName(_pluginName); 174 publisherCriterion.setLabel(new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_PUBLISHER")); 175 publisherCriterion.setSearchable(this); 176 criteria.add(publisherCriterion); 177 178 SearchServiceCriterionDefinition rightsCriterion = new DublinCoreCriterionDefinition<>("rights", ModelItemTypeConstants.STRING_TYPE_ID, Optional.ofNullable(_dcProvider.getEntries("dc_rights"))); 179 rightsCriterion.setName(__CRITERION_DEFINITIONS_PREFIX_ID + "rights"); 180 rightsCriterion.setPluginName(_pluginName); 181 rightsCriterion.setLabel(new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_RIGHTS")); 182 rightsCriterion.setSearchable(this); 183 criteria.add(rightsCriterion); 184 185 // Sitemap criteria 186 SearchServiceCriterionDefinition<String> sitemapCriterion = new SitemapCriterionDefinition(); 187 sitemapCriterion.setName(__CRITERION_DEFINITIONS_PREFIX_ID + "sitemap"); 188 sitemapCriterion.setPluginName(_pluginName); 189 sitemapCriterion.setLabel(new I18nizableText(catalogue, "PLUGINS_WEB_SERVICE_SEARCH_SEARCHABLE_RESOURCE_CRITERION_SITEMAP")); 190 sitemapCriterion.setEnumerator(_sitemapEnumerator); 191 sitemapCriterion.setSearchable(this); 192 criteria.add(sitemapCriterion); 193 194 return criteria; 195 } 196 197 @Override 198 public int criteriaPosition() 199 { 200 return _criteriaPosition; 201 } 202 203 @Override 204 public Optional<Query> joinQuery(Query queryOnCriterion, SearchServiceCriterionDefinition criterion, Collection<Returnable> returnables, AdditionalParameterValueMap additionalParameters) 205 { 206 List<Query> queries = new ArrayList<>(); 207 208 boolean isSubQueryNonEmpty = _isSubQueryNonEmpty(queryOnCriterion); 209 210 if (returnables.contains(_resourceReturnable)) 211 { 212 queries.add(queryOnCriterion); 213 } 214 215 if (returnables.contains(_pageReturnable)) 216 { 217 // join query on visible page attachments 218 if (isSubQueryNonEmpty) 219 { 220 queries.add(_queryOnPage(queryOnCriterion)); 221 queries.add(_queryOnPageThroughContents(queryOnCriterion)); 222 } 223 } 224 225 if (returnables.contains(_contentReturnable)) 226 { 227 // join query on visible content attachments 228 if (isSubQueryNonEmpty) 229 { 230 queries.add(_queryOnContent(queryOnCriterion)); 231 } 232 } 233 234 return queries.size() > 0 ? Optional.of(new OrQuery(queries)) : Optional.empty(); 235 } 236 237 private boolean _isSubQueryNonEmpty(Query queryOnCriterion) 238 { 239 try 240 { 241 return !queryOnCriterion.build().isEmpty(); 242 } 243 catch (QuerySyntaxException e) 244 { 245 // Silently ignore, queryOnCriterion will be built later anyway 246 } 247 return false; 248 } 249 250 private Query _queryOnContent(Query queryOnVisibleContentAttachments) 251 { 252 return new JoinQuery(queryOnVisibleContentAttachments, new JoinKey(SolrFieldNames.CONTENT_VISIBLE_ATTACHMENT_RESOURCE_IDS, SolrFieldNames.RESOURCE_ANCESTOR_AND_SELF_IDS, null)); 253 } 254 255 private Query _queryOnPage(Query queryOnVisiblePageAttachments) 256 { 257 return new JoinQuery(queryOnVisiblePageAttachments, new JoinKey(SolrWebFieldNames.PAGE_VISIBLE_ATTACHMENT_RESOURCE_IDS, SolrFieldNames.RESOURCE_ANCESTOR_AND_SELF_IDS, null)); 258 } 259 260 private Query _queryOnPageThroughContents(Query queryOnVisibleContentAttachments) 261 { 262 return new PageContentQuery(_queryOnContent(queryOnVisibleContentAttachments)); 263 } 264 265 @Override 266 public Collection<Returnable> relationsWith() 267 { 268 return Arrays.asList(_resourceReturnable, _pageReturnable, _contentReturnable); 269 } 270 271 @Override 272 public Query buildQuery( 273 AbstractTreeNode<SearchServiceCriterion<?>> criterionTree, 274 Map<String, Object> userCriteria, 275 Collection<Returnable> returnables, 276 Collection<Searchable> searchables, 277 AdditionalParameterValueMap additionalParameters, 278 String currentLang, 279 Map<String, Object> contextualParameters) 280 { 281 return _searchComponentHelper.buildQuery(criterionTree, userCriteria, returnables, searchables, additionalParameters, currentLang, null, contextualParameters); 282 } 283}