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