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}