001/* 002 * Copyright 2010 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.plugins.tagcloud.generators; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Comparator; 022import java.util.Iterator; 023import java.util.List; 024 025import org.apache.avalon.framework.context.Context; 026import org.apache.avalon.framework.context.ContextException; 027import org.apache.avalon.framework.context.Contextualizable; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.cocoon.ProcessingException; 031import org.apache.cocoon.environment.ObjectModelHelper; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.generation.ServiceableGenerator; 034import org.apache.cocoon.xml.AttributesImpl; 035import org.apache.cocoon.xml.XMLUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.solr.client.solrj.SolrClient; 038import org.apache.solr.client.solrj.SolrQuery; 039import org.xml.sax.SAXException; 040 041import org.ametys.cms.search.query.ContentTypeQuery; 042import org.ametys.cms.search.query.OrQuery; 043import org.ametys.cms.search.query.Query; 044import org.ametys.cms.search.query.QuerySyntaxException; 045import org.ametys.cms.search.solr.SolrClientProvider; 046import org.ametys.plugins.repository.AmetysObjectResolver; 047import org.ametys.plugins.repository.metadata.CompositeMetadata; 048import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 049import org.ametys.plugins.tagcloud.cache.TagCloudCacheManager; 050import org.ametys.runtime.i18n.I18nizableText; 051import org.ametys.web.indexing.solr.SolrWebFieldNames; 052import org.ametys.web.repository.page.Page; 053import org.ametys.web.repository.page.ZoneItem; 054import org.ametys.web.search.query.PageContentQuery; 055import org.ametys.web.search.query.PageQuery; 056 057/** 058 * Generator for tag clouds 059 */ 060public abstract class AbstractTagCloudGenerator extends ServiceableGenerator implements Contextualizable 061{ 062 063 /** Compares tag cloud items */ 064 protected static final Comparator<TagCloudItem> OCCURRENCE_COMPARATOR = new ItemOccurrenceComparator(); 065 066 /** The Ametys object resolver */ 067 protected AmetysObjectResolver _resolver; 068 /** The cache manager */ 069 protected TagCloudCacheManager _cacheManager; 070 071 /** The solr client provider. */ 072 protected SolrClientProvider _solrClientProvider; 073 074 /** The solr client. */ 075 protected SolrClient _solrClient; 076 077 /** The context */ 078 protected Context _context; 079 080 @Override 081 public void contextualize(Context context) throws ContextException 082 { 083 _context = context; 084 } 085 086 @Override 087 public void service(ServiceManager serviceManager) throws ServiceException 088 { 089 super.service(serviceManager); 090 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 091 _cacheManager = (TagCloudCacheManager) serviceManager.lookup(TagCloudCacheManager.ROLE); 092 _solrClientProvider = (SolrClientProvider) serviceManager.lookup(SolrClientProvider.ROLE); 093 _solrClient = _solrClientProvider.getReadClient(); 094 } 095 096 @Override 097 public void generate() throws IOException, SAXException, ProcessingException 098 { 099 Request request = ObjectModelHelper.getRequest(objectModel); 100 ZoneItem zoneItem = (ZoneItem) request.getAttribute(ZoneItem.class.getName()); 101 String siteName = (String) request.getAttribute("site"); 102 String lang = (String) request.getAttribute("sitemapLanguage"); 103 104 // Fetch the needed parameters from the zone item 105 CompositeMetadata serviceParameters = zoneItem.getServiceParameters(); 106 107 // Content types 108 String[] cTypes = serviceParameters.getStringArray("content-types"); 109 110 // Pages 111 String[] pages = serviceParameters.getStringArray("search-by-pages"); 112 113 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 114 if (!_cacheManager.hasTagCloud(currentWsp, zoneItem.getId())) 115 { 116 List<TagCloudItem> tagCloud = getTagCloudItems(siteName, lang, serviceParameters); 117 _cacheManager.addTagCloud(currentWsp, zoneItem.getId(), tagCloud); 118 } 119 120 @SuppressWarnings("unchecked") 121 List<TagCloudItem> tagCloud = (List<TagCloudItem>) _cacheManager.getTagCloud(currentWsp, zoneItem.getId()); 122 123 contentHandler.startDocument(); 124 XMLUtils.startElement(contentHandler, "tagCloud"); 125 126 if (!tagCloud.isEmpty()) 127 { 128 // Limit the number of items 129 int length = (int) serviceParameters.getLong("limit", Integer.MAX_VALUE); // Max number of tags 130 if (length == 0) 131 { 132 length = tagCloud.size(); 133 } 134 int max = tagCloud.get(0).getOccurrenceCount(); 135 int min = tagCloud.size() < length ? tagCloud.get(tagCloud.size() - 1).getOccurrenceCount() : tagCloud.get(length - 1).getOccurrenceCount(); 136 137 Iterator<TagCloudItem> tagCloudIt = tagCloud.iterator(); 138 139 int i = 0; 140 while (tagCloudIt.hasNext() && i < length) 141 { 142 saxTagCloudItem(tagCloudIt.next(), min, max); 143 i++; 144 } 145 146 // Search form values 147 _saxFormParameters(cTypes, pages); 148 149 // Search engine page 150 String pageId = serviceParameters.getString("search-engine-page", null); 151 if (StringUtils.isNotEmpty(pageId)) 152 { 153 Page page = _resolver.resolveById(pageId); 154 XMLUtils.createElement(contentHandler, "searchUrl", page.getSitemapName() + "/" + page.getPathInSitemap()); 155 } 156 } 157 158 XMLUtils.endElement(contentHandler, "tagCloud"); 159 contentHandler.endDocument(); 160 } 161 162 /** 163 * Get the tag cloud items 164 * @param siteName The site name 165 * @param lang The language 166 * @param serviceParameters The service parameters 167 * @return The list og tag cloud item 168 * @throws IOException if an error occurs when manipulating files 169 * @throws ProcessingException if an error occurs during the retrieving of the tag cloud items 170 */ 171 protected abstract List<TagCloudItem> getTagCloudItems(String siteName, String lang, CompositeMetadata serviceParameters) throws IOException, ProcessingException; 172 173 /** 174 * Sax a tag cloud item 175 * @param item The tag cloud item to sax 176 * @param min The min number of occurrence 177 * @param max The max number of occurrence 178 * @throws SAXException if an error occurs while saxing 179 */ 180 protected void saxTagCloudItem(TagCloudItem item, int min, int max) throws SAXException 181 { 182 int nbOccurence = item.getOccurrenceCount(); 183 int position = item.getPosition(); 184 int fontSize = _getFontSize(nbOccurence, min, max); 185 186 AttributesImpl attrs = new AttributesImpl(); 187 attrs.addCDATAAttribute("nb", String.valueOf(nbOccurence)); 188 attrs.addCDATAAttribute("original-position", String.valueOf(position)); 189 attrs.addCDATAAttribute("font-size", String.valueOf(fontSize)); 190 attrs.addCDATAAttribute("frequency", String.valueOf(fontSize + 1)); 191 _saxAdditionalAttributes(item, attrs); 192 193 XMLUtils.startElement(contentHandler, "item", attrs); 194 195 List<String> i18nParams = new ArrayList<>(); 196 i18nParams.add(String.valueOf(fontSize + 1)); 197 new I18nizableText("plugin.tagcloud", "PLUGINS_TAGCLOUD_TAGS_SERVICE_FREQUENCY", i18nParams).toSAX(contentHandler, "frequency"); 198 199 item.getWord().toSAX(contentHandler); 200 201 XMLUtils.endElement(contentHandler, "item"); 202 } 203 204 /** 205 * Build a {@link SolrQuery} from a Query object. 206 * @param query the Query object. 207 * @return The SolrQuery. 208 * @throws QuerySyntaxException if an error occurs. 209 */ 210 protected SolrQuery build(Query query) throws QuerySyntaxException 211 { 212 String queryString = query.build(); 213 214 SolrQuery solrQuery = new SolrQuery(queryString); 215 216 solrQuery.addFilterQuery("_documentType:\"" + SolrWebFieldNames.TYPE_PAGE + "\""); 217 218 // Don't return any rows: we just need the result count. 219 solrQuery.setRows(0); 220 221 return solrQuery; 222 } 223 224 /** 225 * Sax additional attributes for item 226 * @param item The tag cloud item 227 * @param attrs The attributes 228 * @throws SAXException if an error occurs while saxing 229 */ 230 protected void _saxAdditionalAttributes(TagCloudItem item, AttributesImpl attrs) throws SAXException 231 { 232 // Nothing 233 } 234 235 /** 236 * Add content types term query to the queries. 237 * @param queries The query collection. 238 * @param cTypes the content types. Can be empty or null 239 */ 240 protected void _addContentTypeQuery(Collection<Query> queries, String[] cTypes) 241 { 242 if (cTypes != null && cTypes.length > 0 && !(cTypes.length == 1 && cTypes[0].equals(""))) 243 { 244 queries.add(new PageContentQuery(new ContentTypeQuery(cTypes))); 245 } 246 } 247 248 /** 249 * Add pages term query to the queries 250 * @param queries The query collection. 251 * @param pageIds The page IDs. 252 */ 253 protected void _addPagesQuery(Collection<Query> queries, String[] pageIds) 254 { 255 if (pageIds != null && pageIds.length > 0 && !(pageIds.length == 1 && pageIds[0].equals(""))) 256 { 257 List<Query> pageQueries = new ArrayList<>(); 258 259 for (String pageId : pageIds) 260 { 261 pageQueries.add(new PageQuery(pageId, true)); 262 } 263 264 queries.add(new OrQuery(pageQueries)); 265 } 266 } 267 268 /** 269 * SAX teh form search criteria 270 * @param cTypes the content types 271 * @param pages the pages 272 * @throws SAXException if an error occurred while SAXing 273 */ 274 protected void _saxFormParameters (String[] cTypes, String[] pages) throws SAXException 275 { 276 XMLUtils.startElement(contentHandler, "form"); 277 278 _saxContentTypeCriteria(cTypes); 279 _saxSitemapCriteria(pages); 280 281 XMLUtils.endElement(contentHandler, "form"); 282 } 283 284 /** 285 * Get the font size 286 * @param nb the number of occurrence 287 * @param min the min number of occurrence 288 * @param max the max number of occurrence 289 * @return the font size 290 */ 291 protected int _getFontSize (int nb, int min, int max) 292 { 293 double p = ((double) nb - (double) min) / ((double) max - (double) min) * 100.0; 294 double interval = 100.0 / 5.0; // 6 sizes 295 296 return (int) Math.floor(p / interval); 297 } 298 299 300 /** 301 * SAX the content types criteria 302 * @param cTypes the content types 303 * @throws SAXException if an error occurred while SAXing 304 */ 305 protected void _saxContentTypeCriteria (String[] cTypes) throws SAXException 306 { 307 XMLUtils.startElement(contentHandler, "content-types"); 308 309 if (cTypes != null && cTypes.length > 0 && !(cTypes.length == 1 && cTypes[0].equals(""))) 310 { 311 for (String cTypeId : cTypes) 312 { 313 AttributesImpl attr = new AttributesImpl(); 314 attr.addCDATAAttribute("id", cTypeId); 315 XMLUtils.createElement(contentHandler, "type", attr); 316 } 317 } 318 319 XMLUtils.endElement(contentHandler, "content-types"); 320 } 321 322 /** 323 * SAX the pages criteria 324 * @param pages the pages 325 * @throws SAXException if an error occurred while SAXing 326 */ 327 protected void _saxSitemapCriteria (String[] pages) throws SAXException 328 { 329 XMLUtils.startElement(contentHandler, "pages"); 330 331 if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals(""))) 332 { 333 for (String pageID : pages) 334 { 335 Page page = _resolver.resolveById(pageID); 336 AttributesImpl attr = new AttributesImpl(); 337 attr.addCDATAAttribute("path", page.getSitemap().getName() + "/" + page.getPathInSitemap()); 338 attr.addCDATAAttribute("title", page.getTitle()); 339 attr.addCDATAAttribute("id", pageID); 340 XMLUtils.createElement(contentHandler, "page", attr); 341 } 342 } 343 344 XMLUtils.endElement(contentHandler, "pages"); 345 } 346 347// /** 348// * Returns the analyzer to use 349// * @param language The current language 350// * @return The {@link Analyzer} 351// */ 352// protected Analyzer getAnalyzer (String language) 353// { 354// if (language.equals("br")) 355// { 356// return new BrazilianAnalyzer(LuceneConstants.LUCENE_VERSION); 357// } 358// else if (language.equals("cn")) 359// { 360// return new ChineseAnalyzer(); 361// } 362// else if (language.equals("cz")) 363// { 364// return new CzechAnalyzer(LuceneConstants.LUCENE_VERSION); 365// } 366// else if (language.equals("gr")) 367// { 368// return new GreekAnalyzer(LuceneConstants.LUCENE_VERSION); 369// } 370// else if (language.equals("de")) 371// { 372// return new GermanAnalyzer(LuceneConstants.LUCENE_VERSION); 373// } 374// else if (language.equals("fr")) 375// { 376// return new FrenchAnalyzer(LuceneConstants.LUCENE_VERSION); 377// } 378// else if (language.equals("nl")) 379// { 380// return new DutchAnalyzer(LuceneConstants.LUCENE_VERSION); 381// } 382// else if (language.equals("ru")) 383// { 384// return new RussianAnalyzer(LuceneConstants.LUCENE_VERSION); 385// } 386// else if (language.equals("th")) 387// { 388// return new ThaiAnalyzer(LuceneConstants.LUCENE_VERSION); 389// } 390// else 391// { 392// //Default to standard analyzer (works well for english) 393// return new StandardAnalyzer(LuceneConstants.LUCENE_VERSION); 394// } 395// } 396 397// /** 398// * Get the index searcher 399// * 400// * @param siteName The site to search in. 401// * @param lang The current language 402// * @return The index searcher 403// * @throws IOException If an error occurred while opening indexes 404// */ 405// protected Searcher getSearchIndex(String siteName, String lang) throws IOException 406// { 407// File indexDir = IndexerHelper.getIndexDir(_context, siteName, lang); 408// if (indexDir.exists() && indexDir.listFiles().length > 0) 409// { 410// return new IndexSearcher(FSDirectory.open(indexDir)); 411// } 412// else 413// { 414// return null; 415// } 416// } 417 418 /** 419 * Abstract class for a tag cloud item 420 * 421 */ 422 protected interface TagCloudItem 423 { 424 /** 425 * Get the tag cloud word to display 426 * @return The tag cloud word 427 */ 428 I18nizableText getWord(); 429 430 /** 431 * Returns the number of occurrence 432 * @return the number of occurrence 433 */ 434 int getOccurrenceCount(); 435 436 /** 437 * Get the original position. 438 * @return the original position. 439 */ 440 int getPosition(); 441 } 442 443 /** 444 * Compares two terms by descending occurrence count. 445 */ 446 protected static class ItemOccurrenceComparator implements Comparator<TagCloudItem> 447 { 448 449 @Override 450 public int compare(TagCloudItem tc1, TagCloudItem tc2) 451 { 452 return tc2.getOccurrenceCount() - tc1.getOccurrenceCount(); 453 } 454 455 } 456 457}