001/* 002 * Copyright 2013 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 */ 016 017package org.ametys.web.cache.monitoring.ui; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.ProcessingException; 029import org.apache.cocoon.generation.ServiceableGenerator; 030import org.apache.cocoon.xml.AttributesImpl; 031import org.apache.cocoon.xml.XMLUtils; 032import org.apache.commons.lang.StringUtils; 033import org.apache.commons.lang.builder.EqualsBuilder; 034import org.apache.commons.lang.builder.HashCodeBuilder; 035import org.xml.sax.SAXException; 036 037import org.ametys.cms.repository.Content; 038import org.ametys.core.util.DateUtils; 039import org.ametys.plugins.repository.AmetysObjectIterable; 040import org.ametys.plugins.repository.AmetysRepositoryException; 041import org.ametys.plugins.repository.UnknownAmetysObjectException; 042import org.ametys.web.cache.monitoring.process.statistics.ResourceStatisticsComponent; 043import org.ametys.web.inputdata.InputDataExtensionPoint; 044import org.ametys.web.repository.page.Page; 045import org.ametys.web.repository.page.Zone; 046import org.ametys.web.repository.page.ZoneItem; 047import org.ametys.web.repository.page.ZoneItem.ZoneType; 048import org.ametys.web.repository.site.Site; 049import org.ametys.web.repository.site.SiteManager; 050import org.ametys.web.repository.sitemap.Sitemap; 051 052import com.google.common.collect.HashMultimap; 053import com.google.common.collect.LinkedListMultimap; 054import com.google.common.collect.Multimap; 055 056/** 057 * Cache stats generator for related to the back-office (pages and page elements) 058 */ 059public class PageElementCacheStatsGenerator extends ServiceableGenerator 060{ 061 /** Resource statistics component */ 062 protected ResourceStatisticsComponent _resourceStatisticsCmp; 063 064 /** Input Data extension point */ 065 protected InputDataExtensionPoint _inputDataExt; 066 067 /** Ametys resolver */ 068 protected SiteManager _siteManager; 069 070 /** Map linking page Id to page stats entries */ 071 protected Multimap<String, PageStatsEntry> _pageIdMap; 072 073 /** Map of stats entries */ 074 protected Multimap<PageStatsEntry, PageElementStatsEntry> _statsMap; 075 076 @Override 077 public void service(ServiceManager sm) throws ServiceException 078 { 079 super.service(sm); 080 _resourceStatisticsCmp = (ResourceStatisticsComponent) sm.lookup(ResourceStatisticsComponent.ROLE); 081 _inputDataExt = (InputDataExtensionPoint) sm.lookup(InputDataExtensionPoint.ROLE); 082 _siteManager = (SiteManager) sm.lookup(SiteManager.ROLE); 083 084 } 085 086 @Override 087 public void recycle() 088 { 089 _statsMap = null; 090 _pageIdMap = null; 091 092 super.recycle(); 093 } 094 095 @Override 096 public void generate() throws IOException, SAXException, ProcessingException 097 { 098 String siteName = parameters.getParameter("siteName", ""); 099 Site site = null; 100 101 try 102 { 103 site = _siteManager.getSite(siteName); 104 } 105 catch (UnknownAmetysObjectException e) 106 { 107 String message = "The site '" + siteName + "' does not exist."; 108 getLogger().error(message, e); 109 throw new ProcessingException(message, e); 110 } 111 112 _pageIdMap = HashMultimap.create(); // does not allow duplicate key-value entries. 113 _statsMap = LinkedListMultimap.create(); // can contain non-distinct key-value pairs. 114 115 _initializePECacheStats(); 116 117 List<String> contexts = _getContextsFilter(parameters.getParameter("contexts", null)); 118 119 long start = 0; 120 if (getLogger().isDebugEnabled()) 121 { 122 start = System.currentTimeMillis(); 123 124 getLogger().debug("Starting to SAX back-office cache statistics."); 125 } 126 127 contentHandler.startDocument(); 128 XMLUtils.startElement(contentHandler, "stats"); 129 130 // sax info for the given site. 131 _saxStats(site, contexts); 132 133 XMLUtils.endElement(contentHandler, "stats"); 134 contentHandler.endDocument(); 135 136 if (getLogger().isDebugEnabled()) 137 { 138 long end = System.currentTimeMillis(); 139 String duration = DateUtils.formatDuration(end - start); 140 getLogger().debug(String.format("The SAX process of the back-office cache statistics took %s", duration)); 141 } 142 } 143 144 /** 145 * Initialize statistics by retrieving data from the monitoring database. 146 * @throws ProcessingException if an error occurs 147 */ 148 private void _initializePECacheStats() throws ProcessingException 149 { 150 try 151 { 152 List<Map<String, Object>> rawCacheStats = _resourceStatisticsCmp.getPageElementCacheStats(); 153 _processCacheStatsResultSet(rawCacheStats); 154 } 155 catch (Exception e) 156 { 157 String msg = "Error while retrieving page element cache stats"; 158 getLogger().error(msg); 159 throw new ProcessingException(msg, e); 160 } 161 } 162 163 private void _processCacheStatsResultSet(List<Map<String, Object>> rawCacheStats) 164 { 165 for (Map<String, Object> rawCacheStat : rawCacheStats) 166 { 167 PageStatsEntry pageStatsEntry = new PageStatsEntry(rawCacheStat); 168 PageElementStatsEntry pageElementStatsEntry = new PageElementStatsEntry(rawCacheStat); 169 170 _pageIdMap.put(pageStatsEntry._pageId, pageStatsEntry); 171 _statsMap.put(pageStatsEntry, pageElementStatsEntry); 172 } 173 } 174 175 /** 176 * Analyse the 'contexts' parameter to filter the data to sax depending on requested contexts. 177 * A context is a couple as follows : 'renderingContext' - 'workspaceJcr' 178 * @param contexts The contexts 179 * @return the list of filters for the given contexts 180 */ 181 private List<String> _getContextsFilter(String contexts) 182 { 183 if (StringUtils.isEmpty(contexts)) 184 { 185 return null; 186 } 187 188 return Arrays.asList(StringUtils.split(contexts, '#')); 189 } 190 191 private void _saxStats(Site site, List<String> contexts) throws SAXException 192 { 193 AttributesImpl attrs = new AttributesImpl(); 194 attrs.addCDATAAttribute("id", site.getId()); 195 attrs.addCDATAAttribute("name", site.getName()); 196 XMLUtils.startElement(contentHandler, "site", attrs); 197 198 for (Sitemap sitemap : site.getSitemaps()) 199 { 200 _saxStats(sitemap, contexts); 201 } 202 203 XMLUtils.endElement(contentHandler, "site"); 204 } 205 206 private void _saxStats(Sitemap sitemap, List<String> contexts) throws SAXException 207 { 208 AttributesImpl attrs = new AttributesImpl(); 209 attrs.addCDATAAttribute("id", sitemap.getId()); 210 attrs.addCDATAAttribute("name", sitemap.getName()); 211 XMLUtils.startElement(contentHandler, "sitemap", attrs); 212 213 for (Page page : sitemap.getChildrenPages()) 214 { 215 _saxPageStats(page, contexts); 216 } 217 218 XMLUtils.endElement(contentHandler, "sitemap"); 219 } 220 221 private void _saxPageStats(Page page, List<String> contexts) throws SAXException 222 { 223 String pageId = page.getId(); 224 225 // SAX'ing page info 226 AttributesImpl attrs = new AttributesImpl(); 227 attrs.addCDATAAttribute("id", pageId); 228 attrs.addCDATAAttribute("title", page.getTitle()); 229 attrs.addCDATAAttribute("name", page.getName()); 230 attrs.addCDATAAttribute("path", page.getPathInSitemap()); 231 XMLUtils.startElement(contentHandler, "page", attrs); 232 233 // SAX stats by contexts 234 if (_pageIdMap.containsKey(pageId)) 235 { 236 List<Zone> zones = new ArrayList<>(); 237 for (Zone zone : page.getZones()) 238 { 239 zones.add(zone); 240 } 241 242 _saxPageStatsContexts(page, zones, contexts); 243 } 244 245 for (Page child : page.getChildrenPages()) 246 { 247 _saxPageStats(child, contexts); 248 } 249 250 XMLUtils.endElement(contentHandler, "page"); 251 } 252 253 private void _saxPageStatsContexts(Page page, List<Zone> zones, List<String> contexts) throws SAXException 254 { 255 Collection<PageStatsEntry> pageStatsEntries = _pageIdMap.get(page.getId()); 256 257 for (PageStatsEntry pageStats : pageStatsEntries) 258 { 259 // Filter out contexts that are not in the 'contexts' list. 260 // null lists implies that all contexts are allowed 261 if (contexts == null || contexts.contains(pageStats.getContext())) 262 { 263 _saxPageStatsContext(pageStats, zones); 264 } 265 } 266 } 267 268 private void _saxPageStatsContext(PageStatsEntry pageStats, List<Zone> zones) throws SAXException 269 { 270 AttributesImpl attrs = new AttributesImpl(); 271 attrs.addCDATAAttribute("renderingContext", pageStats._renderingContext); 272 attrs.addCDATAAttribute("workspaceJCR", pageStats._workspaceJCR); 273 attrs.addCDATAAttribute("cacheable", String.valueOf(pageStats._cacheable)); 274 XMLUtils.startElement(contentHandler, "context", attrs); 275 276 XMLUtils.createElement(contentHandler, "hits", String.valueOf(pageStats._hits)); 277 278 Collection<PageElementStatsEntry> pageElementStats = _statsMap.get(pageStats); 279 280 // Iterate on existing zones 281 for (Zone zone : zones) 282 { 283 _saxZoneStats(zone, pageElementStats); 284 } 285 286 // Iterate on input data 287 _saxInputDataStats(pageElementStats); 288 289 XMLUtils.endElement(contentHandler, "context"); 290 } 291 292 private void _saxZoneStats(Zone zone, Collection<PageElementStatsEntry> pageElementStats) throws SAXException 293 { 294 AttributesImpl attrs = new AttributesImpl(); 295 attrs.addCDATAAttribute("id", zone.getId()); 296 attrs.addCDATAAttribute("name", zone.getName()); 297 XMLUtils.startElement(contentHandler, "zone", attrs); 298 299 AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems(); 300 int index = 0; 301 for (ZoneItem zoneItem : zoneItems) 302 { 303 index++; 304 PageElementStatsEntry entry = _findPageElementStatsEntry(pageElementStats, zoneItem.getId()); 305 if (entry != null) 306 { 307 _saxZoneItemStats(zoneItem, entry, index); 308 } 309 } 310 311 XMLUtils.endElement(contentHandler, "zone"); 312 } 313 314 315 private void _saxZoneItemStats(ZoneItem zoneItem, PageElementStatsEntry entry, int index) throws SAXException 316 { 317 AttributesImpl attrs = new AttributesImpl(); 318 attrs.addCDATAAttribute("id", entry._pageElementID); 319 attrs.addCDATAAttribute("index", String.valueOf(index)); 320 attrs.addCDATAAttribute("type", zoneItem.getType().toString().toLowerCase()); 321 attrs.addCDATAAttribute("cacheable", String.valueOf(entry._cacheable)); 322 _addZoneItemSaxInfo(attrs, zoneItem); 323 XMLUtils.startElement(contentHandler, "zoneitem", attrs); 324 325 XMLUtils.createElement(contentHandler, "hits", String.valueOf(entry._hits)); 326 XMLUtils.createElement(contentHandler, "cacheHits", String.valueOf(entry._cacheHits)); 327 328 XMLUtils.endElement(contentHandler, "zoneitem"); 329 } 330 331 private void _addZoneItemSaxInfo(AttributesImpl attrs, ZoneItem zoneItem) 332 { 333 try 334 { 335 switch (zoneItem.getType()) 336 { 337 case CONTENT: 338 Content content = zoneItem.getContent(); 339 attrs.addCDATAAttribute("contentId", content.getId()); 340 attrs.addCDATAAttribute("contentType", StringUtils.join(content.getTypes())); 341 break; 342 case SERVICE: 343 attrs.addCDATAAttribute("serviceId", zoneItem.getServiceId()); 344 break; 345 default: 346 throw new IllegalArgumentException("Illegal zone item type. Allowed values are : " + Arrays.asList(ZoneType.values())); 347 } 348 } 349 catch (AmetysRepositoryException e) 350 { 351 // Ignore 352 } 353 } 354 355 private void _saxInputDataStats(Collection<PageElementStatsEntry> pageElementStats) throws SAXException 356 { 357 XMLUtils.startElement(contentHandler, "inputdata"); 358 359 for (String id : _inputDataExt.getExtensionsIds()) 360 { 361 PageElementStatsEntry entry = _findPageElementStatsEntry(pageElementStats, id); 362 if (entry != null) 363 { 364 _saxInputDataStatsEntry(entry, id); 365 } 366 } 367 368 XMLUtils.endElement(contentHandler, "inputdata"); 369 } 370 371 private void _saxInputDataStatsEntry(PageElementStatsEntry entry, String inputDataId) throws SAXException 372 { 373 AttributesImpl attrs = new AttributesImpl(); 374 attrs.addCDATAAttribute("id", inputDataId); 375 attrs.addCDATAAttribute("title", inputDataId); 376 attrs.addCDATAAttribute("cacheable", String.valueOf(entry._cacheable)); 377 XMLUtils.startElement(contentHandler, "inputdataitem", attrs); 378 379 XMLUtils.createElement(contentHandler, "hits", String.valueOf(entry._hits)); 380 XMLUtils.createElement(contentHandler, "cacheHits", String.valueOf(entry._cacheHits)); 381 382 XMLUtils.endElement(contentHandler, "inputdataitem"); 383 } 384 385 private PageElementStatsEntry _findPageElementStatsEntry(Collection<PageElementStatsEntry> pageElementStats, String id) 386 { 387 for (PageElementStatsEntry entry : pageElementStats) 388 { 389 if (id.equals(entry._pageElementID)) 390 { 391 return entry; 392 } 393 } 394 return null; 395 } 396 397 /** 398 * Object model representing an entry of stats for a Page 399 */ 400 protected class PageStatsEntry 401 { 402 /** page id */ 403 protected final String _pageId; 404 /** rendering context */ 405 protected final String _renderingContext; 406 /** workspace JCR */ 407 protected final String _workspaceJCR; 408 /** is cacheable */ 409 protected final boolean _cacheable; 410 /** hits */ 411 protected final int _hits; 412 413 /** 414 * Ctor 415 * @param data map of raw data object. 416 */ 417 protected PageStatsEntry(Map<String, Object> data) 418 { 419 _pageId = (String) data.get("PAGE_ID"); 420 _renderingContext = (String) data.get("RENDERING_CONTEXT"); 421 _workspaceJCR = (String) data.get("WORKSPACE_JCR"); 422 _cacheable = (boolean) data.get("P_CACHEABLE"); 423 _hits = (int) data.get("P_HITS"); 424 } 425 426 /** 427 * Returns the formatted context name 428 * @return context name 429 */ 430 public String getContext() 431 { 432 return _renderingContext + '-' + _workspaceJCR; 433 } 434 435 @Override 436 public boolean equals(Object obj) 437 { 438 if (obj == null) 439 { 440 return false; 441 } 442 if (obj == this) 443 { 444 return true; 445 } 446 if (!(obj instanceof PageStatsEntry)) 447 { 448 return false; 449 } 450 451 PageStatsEntry that = (PageStatsEntry) obj; 452 453 EqualsBuilder eb = new EqualsBuilder(); 454 eb.append(_pageId, that._pageId); 455 eb.append(_renderingContext, that._renderingContext); 456 eb.append(_workspaceJCR, that._workspaceJCR); 457 return eb.isEquals(); 458 } 459 460 @Override 461 public int hashCode() 462 { 463 HashCodeBuilder hsb = new HashCodeBuilder(); 464 hsb.append(_pageId).append(_renderingContext).append(_workspaceJCR); 465 return hsb.toHashCode(); 466 } 467 } 468 469 /** 470 * Object model representing an entry of stats for a PageElement 471 */ 472 protected class PageElementStatsEntry 473 { 474 /** page element id */ 475 protected final String _pageElementID; 476 /** rendering context */ 477 protected final String _renderingContext; 478 /** workspace JCR */ 479 protected final String _workspaceJCR; 480 /** is cacheable */ 481 protected final boolean _cacheable; 482 /** hits */ 483 protected final int _hits; 484 /** cache hits */ 485 protected final int _cacheHits; 486 487 /** 488 * Ctor 489 * @param data map of raw data object. 490 */ 491 protected PageElementStatsEntry(Map<String, Object> data) 492 { 493 _pageElementID = (String) data.get("PAGE_ELEMENT_ID"); 494 _renderingContext = (String) data.get("RENDERING_CONTEXT"); 495 _workspaceJCR = (String) data.get("WORKSPACE_JCR"); 496 _cacheable = (boolean) data.get("PE_CACHEABLE"); 497 _hits = (int) data.get("PE_HITS"); 498 _cacheHits = (int) data.get("PE_CACHE_HITS"); 499 } 500 } 501}