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