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.sql.Clob; 021import java.sql.SQLException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.regex.Matcher; 032 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.cocoon.ProcessingException; 036import org.apache.cocoon.generation.ServiceableGenerator; 037import org.apache.cocoon.xml.AttributesImpl; 038import org.apache.cocoon.xml.XMLUtils; 039import org.apache.commons.lang3.StringUtils; 040import org.xml.sax.SAXException; 041 042import org.ametys.core.util.DateUtils; 043import org.ametys.plugins.repository.UnknownAmetysObjectException; 044import org.ametys.web.cache.monitoring.process.statistics.ResourceStatisticsComponent; 045import org.ametys.web.repository.site.Site; 046import org.ametys.web.repository.site.SiteManager; 047import org.ametys.web.site.SitesGenerator; 048 049import com.google.common.collect.ArrayListMultimap; 050import com.google.common.collect.HashMultimap; 051import com.google.common.collect.Multimap; 052 053 054/** 055 * Cache stats generator grouping data collected across each server cache (http 056 * server/front/back) 057 */ 058public class ServersCacheStatsGenerator extends ServiceableGenerator 059{ 060 /** List of paths used to during the sanitize process of the server path */ 061 protected static final String[] _SPECIAL_PATH_PREFIXS = new String[]{"/skins/", "/plugins/", "/kernel/", "/_external/"}; 062 063 /** Resource statistics component */ 064 protected ResourceStatisticsComponent _resourceStatisticsCmp; 065 066 /** Ametys resolver */ 067 protected SiteManager _siteManager; 068 069 /** 070 * This multimap associates site names to a list of prefix. This is needed 071 * to sanitize the server path. 072 */ 073 protected Multimap<String, String> _pathSanitizer; 074 075 /** 076 * Multimaps representing the all the paths to the resources in a recursive 077 * way. The map key's are the site names. 078 */ 079 protected Map<String, Multimap<String, String>> _pathMaps; 080 081 /** 082 * Multimap containing httpserver stats entries classified by site name and path 083 */ 084 protected Map<String, Map<String, FrontFromHTTPServerStatsEntry>> _fromHTTPServerStats; 085 086 /** 087 * Multimap containing (only) front stats entries classified by site name 088 * and path 089 */ 090 protected Map<String, Map<String, FrontFromFrontStatsEntry>> _fromFrontOnlyStats; 091 092 /** Map containing back stats entries classified by path */ 093 protected Map<String, BackStatsEntry> _backStats; 094 095 @Override 096 public void service(ServiceManager sm) throws ServiceException 097 { 098 super.service(sm); 099 _resourceStatisticsCmp = (ResourceStatisticsComponent) sm.lookup(ResourceStatisticsComponent.ROLE); 100 _siteManager = (SiteManager) sm.lookup(SiteManager.ROLE); 101 } 102 103 @Override 104 public void generate() throws IOException, SAXException, ProcessingException 105 { 106 String siteName = parameters.getParameter("siteName", ""); 107 Site site = null; 108 109 try 110 { 111 site = _siteManager.getSite(siteName); 112 } 113 catch (UnknownAmetysObjectException e) 114 { 115 String message = "The site '" + siteName + "' does not exist."; 116 getLogger().error(message, e); 117 throw new ProcessingException(message, e); 118 } 119 120 // Retrieve raw info from DB. 121 List<RawStatsEntry> rawStats = _getStatsFromDb(siteName); 122 123 _pathMaps = new HashMap<>(); 124 _fromHTTPServerStats = new HashMap<>(); 125 _fromFrontOnlyStats = new HashMap<>(); 126 _backStats = new HashMap<>(); 127 _pathSanitizer = ArrayListMultimap.create(); 128 129 // Populate and organize stats maps. 130 _initializeStats(rawStats); 131 132 long start = 0; 133 if (getLogger().isDebugEnabled()) 134 { 135 start = System.currentTimeMillis(); 136 137 getLogger().debug("Starting to SAX cache statistics for HTTPServer and the Front-office"); 138 } 139 140 // Start SAX'ing 141 contentHandler.startDocument(); 142 XMLUtils.startElement(contentHandler, "stats"); 143 144 // sax info for the given site. 145 _saxStatsBySite(site); 146 147 // sax orphan (resource from front without site info). 148 _saxStatsOrphanEntries(); 149 150 XMLUtils.endElement(contentHandler, "stats"); 151 contentHandler.endDocument(); 152 153 if (getLogger().isDebugEnabled()) 154 { 155 long end = System.currentTimeMillis(); 156 String duration = DateUtils.formatDuration(end - start); 157 getLogger().debug(String.format("The SAX process of the HTTPServer and Front-office cache statistics took %s", duration)); 158 } 159 } 160 161 @Override 162 public void recycle() 163 { 164 _pathMaps = null; 165 _fromHTTPServerStats = null; 166 _fromFrontOnlyStats = null; 167 _backStats = null; 168 _pathSanitizer = null; 169 170 super.recycle(); 171 } 172 173 /** 174 * Retrieves raw statistics information 175 * @param siteName The site name 176 * @return a list of {@link RawStatsEntry} 177 * @throws ProcessingException if an error occurs 178 */ 179 private List<RawStatsEntry> _getStatsFromDb(String siteName) throws ProcessingException 180 { 181 try 182 { 183 List<Map<String, Object>> rawCacheStats = _resourceStatisticsCmp.getServerCacheStats(siteName); 184 return _processCacheStatsResultSet(rawCacheStats); 185 } 186 catch (Exception e) 187 { 188 String msg = "Error while retrieving cache stats"; 189 getLogger().error(msg); 190 throw new ProcessingException(msg, e); 191 } 192 } 193 194 private List<RawStatsEntry> _processCacheStatsResultSet(List<Map<String, Object>> rawCacheStats) throws SQLException 195 { 196 List<RawStatsEntry> entries = new ArrayList<>(); 197 198 for (Map<String, Object> rawCacheStat : rawCacheStats) 199 { 200 entries.add(new RawStatsEntry(rawCacheStat)); 201 } 202 203 return entries; 204 } 205 206 /** 207 * Organize the different Map of stats 208 * @param rawStats the raw statistics 209 */ 210 private void _initializeStats(List<RawStatsEntry> rawStats) 211 { 212 // Iterate over raw entries and do the logic to populate 213 // HTTPServer/FrontOnly/Back stats map. 214 for (RawStatsEntry rawStatsEntry : rawStats) 215 { 216 if (rawStatsEntry.hasHTTPServerInfo()) 217 { 218 FrontFromHTTPServerStatsEntry httpServerEntry = new FrontFromHTTPServerStatsEntry(rawStatsEntry._data); 219 _addToHTTPServerStats(httpServerEntry); 220 } 221 else 222 { 223 FrontFromFrontStatsEntry frontEntry = new FrontFromFrontStatsEntry(rawStatsEntry._data); 224 _addToFrontOnlyStats(frontEntry); 225 } 226 227 if (rawStatsEntry.hasBackInfo()) 228 { 229 BackStatsEntry backEntry = new BackStatsEntry(rawStatsEntry._data); 230 231 // remove .html in page name to avoid issues when a page as children pages. 232 String backPath = StringUtils.removeEnd(backEntry._pagePath, ".html"); 233 _backStats.put(backPath, backEntry); 234 } 235 } 236 } 237 238 private void _addToHTTPServerStats(FrontFromHTTPServerStatsEntry httpServerEntry) 239 { 240 String siteName = httpServerEntry._serverSite; 241 242 Map<String, FrontFromHTTPServerStatsEntry> siteMap = _fromHTTPServerStats.get(siteName); 243 if (siteMap == null) 244 { 245 siteMap = new HashMap<>(); 246 _fromHTTPServerStats.put(siteName, siteMap); 247 } 248 249 // The path must be sanitized to be of the following form: 250 // /site/lang/page-path or /skin/... or /plugins/... etc... 251 String sanitizedPath = httpServerEntry.getSanitizedPath(); 252 253 // remove .html in page name to avoid issues when a page as children pages. 254 sanitizedPath = StringUtils.removeEnd(sanitizedPath, ".html"); 255 256 FrontFromHTTPServerStatsEntry previousHTTPServerEntry = siteMap.put(sanitizedPath, httpServerEntry); 257 258 // Must merge the info if a previous entry was already in the map (this can happens when a site as several url aliases) 259 if (previousHTTPServerEntry != null) 260 { 261 httpServerEntry.merge(previousHTTPServerEntry); 262 } 263 264 // Also register this new path into the path maps. 265 _registerPath(httpServerEntry._serverSite, sanitizedPath); 266 } 267 268 private void _addToFrontOnlyStats(FrontFromFrontStatsEntry frontEntry) 269 { 270 String siteName = frontEntry._frontSite; 271 272 Map<String, FrontFromFrontStatsEntry> siteMap = _fromFrontOnlyStats.get(siteName); 273 if (siteMap == null) 274 { 275 siteMap = new HashMap<>(); 276 _fromFrontOnlyStats.put(siteName, siteMap); 277 } 278 279 // remove .html in page name to avoid issues when a page as children pages. 280 String frontPath = StringUtils.removeEnd(frontEntry._frontPath, ".html"); 281 siteMap.put(frontPath, frontEntry); 282 283 // Also register this new path into the path maps. 284 _registerPath(siteName, frontPath); 285 } 286 287 private void _registerPath(String serverSite, String path) 288 { 289 Multimap<String, String> pathMap = _pathMaps.get(serverSite); 290 if (pathMap == null) 291 { 292 pathMap = HashMultimap.create(); // does not allow duplicate key-value entries. 293 _pathMaps.put(serverSite, pathMap); 294 } 295 296 String[] splitPath = StringUtils.split(path, '/'); 297 _internalRegisterPath(pathMap, StringUtils.EMPTY, new LinkedList<>(Arrays.asList(splitPath))); 298 } 299 300 /** 301 * Populate the path map using recursion. 302 * @param pathMap Multimap representing the paths to the resources within a site. 303 * @param consumed the consumed part of the current path being registered. 304 * @param tail tail of the current path being registered. The tail will be consumed in the recursive nested calls of this internal function. 305 */ 306 private void _internalRegisterPath(Multimap<String, String> pathMap, String consumed, LinkedList<String> tail) 307 { 308 if (!tail.isEmpty()) 309 { 310 String head = tail.removeFirst(); 311 pathMap.put(consumed, head); 312 _internalRegisterPath(pathMap, consumed + '/' + head, tail); 313 } 314 } 315 316 private void _saxStatsBySite(Site site) throws SAXException 317 { 318 String siteName = site.getName(); 319 320 Multimap<String, String> pathMap = _pathMaps.get(siteName); 321 if (pathMap == null) 322 { 323 return; 324 } 325 326 Collection<String> entryPoints = pathMap.get(StringUtils.EMPTY); 327 if (entryPoints.isEmpty()) 328 { 329 return; 330 } 331 332 Map<String, FrontFromHTTPServerStatsEntry> httpServerStats = _fromHTTPServerStats.get(siteName); 333 if (httpServerStats == null) 334 { 335 httpServerStats = Collections.EMPTY_MAP; 336 } 337 338 Map<String, FrontFromFrontStatsEntry> frontOnlyStats = _fromFrontOnlyStats.get(siteName); 339 if (frontOnlyStats == null) 340 { 341 frontOnlyStats = Collections.EMPTY_MAP; 342 } 343 344 AttributesImpl attrs = new AttributesImpl(); 345 attrs.addCDATAAttribute("name", siteName); 346 attrs.addCDATAAttribute("id", site.getId()); 347 XMLUtils.startElement(contentHandler, "site", attrs); 348 349 for (String name : entryPoints) 350 { 351 _saxStatsEntry(name, '/' + name, pathMap, httpServerStats, frontOnlyStats); 352 } 353 354 XMLUtils.endElement(contentHandler, "site"); 355 } 356 357 private void _saxStatsEntry(String name, String path, Multimap<String, String> pathMap, Map<String, FrontFromHTTPServerStatsEntry> httpServerEntries, Map<String, FrontFromFrontStatsEntry> frontEntries) throws SAXException 358 { 359 FrontFromHTTPServerStatsEntry httpServerEntry = httpServerEntries.get(path); 360 FrontFromFrontStatsEntry frontEntry = frontEntries.get(path); 361 BackStatsEntry backEntry = _backStats.get(path); 362 363 AttributesImpl attrs = new AttributesImpl(); 364 365 String type = "folder"; 366 if (frontEntry != null) 367 { 368 type = frontEntry._frontPath.endsWith(".html") ? "page" : "asset"; 369 } 370 else if (httpServerEntry != null) 371 { 372 if (httpServerEntry.hasFrontInfo()) 373 { 374 type = httpServerEntry._frontPath.endsWith(".html") ? "page" : "asset"; 375 } 376 else 377 { 378 type = httpServerEntry._serverPath.endsWith(".html") ? "page" : "asset"; 379 } 380 } 381 382 Boolean cacheable = null; 383 if (httpServerEntry != null) 384 { 385 // No front info means implicit cache hit on the httpserver cache. 386 cacheable = httpServerEntry.hasFrontInfo() ? httpServerEntry._frontCacheable : true; 387 } 388 else if (frontEntry != null) 389 { 390 cacheable = frontEntry._frontCacheable; 391 } 392 393 attrs.addCDATAAttribute("type", type); 394 attrs.addCDATAAttribute("name", "page".equals(type) ? name + ".html" : name); 395 attrs.addCDATAAttribute("path", path); 396 __addAttrIfNotNull(attrs, "cacheable", cacheable); 397 XMLUtils.startElement(contentHandler, "resource", attrs); 398 399 _saxStatsHTTPServerEntry(httpServerEntry); 400 _saxStatsFrontEntry(frontEntry); 401 _saxStatsBackEntry(backEntry); 402 403 // Recursive call on children 404 for (String childName : pathMap.get(path)) 405 { 406 _saxStatsEntry(childName, path + '/' + childName, pathMap, httpServerEntries, frontEntries); 407 } 408 409 XMLUtils.endElement(contentHandler, "resource"); 410 } 411 412 private void _saxStatsHTTPServerEntry(FrontFromHTTPServerStatsEntry httpServerEntry) throws SAXException 413 { 414 if (httpServerEntry == null) 415 { 416 return; 417 } 418 419 AttributesImpl attrs = new AttributesImpl(); 420 attrs.addCDATAAttribute("type", "httpserver"); 421 __addAttrIfNotNull(attrs, "httpServerSite", httpServerEntry._serverSite); 422 __addAttrIfNotNull(attrs, "httpServerPath", httpServerEntry._serverPath); 423 424 // Add front info if available 425 if (httpServerEntry.hasFrontInfo()) 426 { 427 __addAttrIfNotNull(attrs, "frontSite", httpServerEntry._frontSite); 428 attrs.addCDATAAttribute("frontPath", httpServerEntry._frontPath); 429 attrs.addCDATAAttribute("frontCacheable", String.valueOf(httpServerEntry._frontCacheable)); 430 } 431 432 XMLUtils.startElement(contentHandler, "entry", attrs); 433 434 XMLUtils.createElement(contentHandler, "httpServerHits", String.valueOf(httpServerEntry._serverHits)); 435 XMLUtils.createElement(contentHandler, "httpServerCacheHits", String.valueOf(httpServerEntry._serverCacheHits)); 436 437 // Add front info if available 438 if (httpServerEntry.hasFrontInfo()) 439 { 440 XMLUtils.createElement(contentHandler, "frontHits", String.valueOf(httpServerEntry._frontHits)); 441 XMLUtils.createElement(contentHandler, "frontCacheHits1", String.valueOf(httpServerEntry._frontCacheHits1)); 442 XMLUtils.createElement(contentHandler, "frontCacheHits2", String.valueOf(httpServerEntry._frontCacheHits2)); 443 } 444 445 XMLUtils.endElement(contentHandler, "entry"); 446 } 447 448 private void _saxStatsFrontEntry(FrontFromFrontStatsEntry frontEntry) throws SAXException 449 { 450 if (frontEntry == null) 451 { 452 return; 453 } 454 455 AttributesImpl attrs = new AttributesImpl(); 456 attrs.addCDATAAttribute("type", "front"); 457 __addAttrIfNotNull(attrs, "site", frontEntry._frontSite); 458 attrs.addCDATAAttribute("path", frontEntry._frontPath); 459 attrs.addCDATAAttribute("cacheable", String.valueOf(frontEntry._frontCacheable)); 460 XMLUtils.startElement(contentHandler, "entry", attrs); 461 462 XMLUtils.createElement(contentHandler, "hits", String.valueOf(frontEntry._frontHits)); 463 XMLUtils.createElement(contentHandler, "cacheHits1", String.valueOf(frontEntry._frontCacheHits1)); 464 XMLUtils.createElement(contentHandler, "cacheHits2", String.valueOf(frontEntry._frontCacheHits2)); 465 466 XMLUtils.endElement(contentHandler, "entry"); 467 } 468 469 private void _saxStatsBackEntry(BackStatsEntry backEntry) throws SAXException 470 { 471 if (backEntry == null) 472 { 473 return; 474 } 475 476 AttributesImpl attrs = new AttributesImpl(); 477 attrs.addCDATAAttribute("type", "back"); 478 attrs.addCDATAAttribute("id", backEntry._pageId); 479 attrs.addCDATAAttribute("path", backEntry._pagePath); 480 attrs.addCDATAAttribute("cacheable", String.valueOf(backEntry._cacheable)); 481 XMLUtils.startElement(contentHandler, "entry", attrs); 482 483 XMLUtils.createElement(contentHandler, "hits", String.valueOf(backEntry._hits)); 484 485 XMLUtils.endElement(contentHandler, "entry"); 486 } 487 488 private void _saxStatsOrphanEntries() throws SAXException 489 { 490 Multimap<String, String> pathMap = _pathMaps.get("-"); 491 if (pathMap == null || pathMap.isEmpty()) 492 { 493 return; 494 } 495 496 Collection<String> entryPoints = pathMap.get(StringUtils.EMPTY); 497 if (entryPoints.isEmpty()) 498 { 499 return; 500 } 501 502 Map<String, FrontFromHTTPServerStatsEntry> httpServerStats = _fromHTTPServerStats.get("-"); 503 if (httpServerStats == null) 504 { 505 httpServerStats = Collections.EMPTY_MAP; 506 } 507 508 Map<String, FrontFromFrontStatsEntry> frontOnlyStats = _fromFrontOnlyStats.get("-"); 509 if (frontOnlyStats == null) 510 { 511 frontOnlyStats = Collections.EMPTY_MAP; 512 } 513 514 XMLUtils.startElement(contentHandler, "orphans"); 515 516 for (String name : entryPoints) 517 { 518 _saxStatsEntry(name, '/' + name, pathMap, httpServerStats, frontOnlyStats); 519 } 520 521 XMLUtils.endElement(contentHandler, "orphans"); 522 } 523 524 private void __addAttrIfNotNull(AttributesImpl attrs, String localName, String value) 525 { 526 if (StringUtils.isNotEmpty(value)) 527 { 528 attrs.addCDATAAttribute(localName, value); 529 } 530 } 531 532 private void __addAttrIfNotNull(AttributesImpl attrs, String localName, Boolean value) 533 { 534 if (value != null) 535 { 536 attrs.addCDATAAttribute(localName, String.valueOf(value)); 537 } 538 } 539 540 /** 541 * Object model representing a raw entry of stats retrieved through the DB. 542 */ 543 protected class RawStatsEntry 544 { 545 /** The map of raw data objects */ 546 protected final Map<String, Object> _data; 547 548 /** 549 * Constructor 550 * @param data map of raw data object. 551 * @throws SQLException if an error occurs while retrieving statistics in SQL database 552 */ 553 protected RawStatsEntry(Map<String, Object> data) throws SQLException 554 { 555 // Mandatory pre-processing of data that contains a Path. 556 // It must be done while the underlying ResultSet object is not 557 // closed to correctly handle CLOB to String conversion. 558 for (String key : data.keySet()) 559 { 560 if (key.contains("PATH")) 561 { 562 data.put(key, __textToString(data.get(key))); 563 } 564 } 565 566 _data = data; 567 } 568 569 /** 570 * Indicates if this instance has httpserver information. 571 * @return a boolean 572 */ 573 protected boolean hasHTTPServerInfo() 574 { 575 String serverPath = (String) _data.get("SERVER_PATH"); 576 return serverPath != null && !"-".equals(serverPath); 577 } 578 579 /** 580 * Indicates if this instance has back(-office) information. 581 * @return a boolean 582 */ 583 protected boolean hasBackInfo() 584 { 585 String pageId = (String) _data.get("PAGE_ID"); 586 return pageId != null && !"-".equals(pageId); 587 } 588 589 /** 590 * Utility method 591 * Object representing a text to String 592 * @param object The object to convert 593 * @return the boolean 594 * @throws SQLException if an error occurs while retrieving statistics in SQL database 595 */ 596 protected String __textToString(Object object) throws SQLException 597 { 598 if (object == null) 599 { 600 return null; 601 } 602 603 if (object instanceof String) 604 { 605 return (String) object; 606 } 607 else if (object instanceof Clob) 608 { 609 Clob clob = (Clob) object; 610 return clob.getSubString(1, (int) clob.length()); 611 } 612 613 throw new IllegalArgumentException("Not able to convert text object to String : unexpected object type."); 614 } 615 } 616 617 /** 618 * Object model representing an entry of stats for a front resource, coming 619 * from httpserver 620 */ 621 protected class FrontFromHTTPServerStatsEntry extends FrontFromFrontStatsEntry 622 { 623 /** Server site name */ 624 protected final String _serverSite; 625 /** Server path */ 626 protected final String _serverPath; 627 /** server hits */ 628 protected int _serverHits; 629 /** server cache hits */ 630 protected int _serverCacheHits; 631 632 /** 633 * Ctor 634 * @param data map of raw data object. 635 */ 636 protected FrontFromHTTPServerStatsEntry(Map<String, Object> data) 637 { 638 super(data); 639 640 _serverSite = (String) data.get("SERVER_SITE"); 641 _serverPath = (String) data.get("SERVER_PATH"); 642 _serverHits = (int) data.get("SERVER_HITS"); 643 _serverCacheHits = (int) data.get("SERVER_CACHE_HITS"); 644 } 645 646 /** 647 * Merge this instance of <code>FrontFromHTTPServerStatsEntry</code> with 648 * another instance of <code>FrontFromHTTPServerStatsEntry</code> 649 * @param that the stats entry to merhe with 650 */ 651 protected void merge(FrontFromHTTPServerStatsEntry that) 652 { 653 if (that == null) 654 { 655 return; 656 } 657 658 _serverHits += that._serverHits; 659 _serverCacheHits += that._serverCacheHits; 660 661 if (that.hasFrontInfo()) 662 { 663 _frontSite = that._frontSite; 664 _frontPath = that._frontPath; 665 _frontCacheable = that._frontCacheable; 666 667 _frontHits = hasFrontInfo() ? _frontHits + that._frontHits : that._frontHits; 668 _frontCacheHits1 = hasFrontInfo() ? _frontCacheHits1 + that._frontCacheHits1 : that._frontCacheHits1; 669 _frontCacheHits2 = hasFrontInfo() ? _frontCacheHits2 + that._frontCacheHits2 : that._frontCacheHits2; 670 } 671 } 672 673 /** 674 * Returns a sanitized path given the different path properties of this 675 * instance. 676 * @return The sanitized path 677 */ 678 protected String getSanitizedPath() 679 { 680 if (hasFrontInfo()) 681 { 682 return _frontPath; 683 } 684 685 // initialize allowed prefix path list for this site, given the site url aliases. 686 if (!_pathSanitizer.containsKey(_serverSite)) 687 { 688 for (String url : _siteManager.getSite(_serverSite).getUrlAliases()) 689 { 690 Matcher matcher = SitesGenerator.URL_PATTERN.matcher(url); 691 if (matcher.matches()) 692 { 693 String path = matcher.group(5); 694 if (StringUtils.isNotEmpty(path)) 695 { 696 _pathSanitizer.put(_serverSite, path); 697 } 698 } 699 } 700 701 if (!_pathSanitizer.containsKey(_serverSite)) 702 { 703 _pathSanitizer.put(_serverSite, null); 704 } 705 } 706 707 // sanitize serverPath 708 Iterator<String> sitePathPrefixIt = _pathSanitizer.get(_serverSite).iterator(); 709 boolean sanitized = false; 710 String sanitizedPath = _serverPath; 711 712 while (!sanitized && sitePathPrefixIt.hasNext()) 713 { 714 String prefix = sitePathPrefixIt.next(); 715 716 // Nothing to do in this case. 717 if (prefix == null) 718 { 719 sanitized = true; 720 } 721 else if (StringUtils.startsWith(_serverPath, prefix)) 722 { 723 sanitizedPath = StringUtils.removeStart(_serverPath, prefix); 724 sanitized = true; 725 } 726 } 727 728 // add site name when needed 729 if (!StringUtils.startsWithAny(sanitizedPath, _SPECIAL_PATH_PREFIXS)) 730 { 731 sanitizedPath = '/' + _serverSite + sanitizedPath; 732 } 733 734 return sanitizedPath; 735 } 736 737 /** 738 * Indicates if this instance has front(-office) information. 739 * @return a boolean 740 */ 741 protected boolean hasFrontInfo() 742 { 743 return _frontPath != null && !"-".equals(_frontPath); 744 } 745 } 746 747 /** 748 * Object model representing an entry of stats for a front resource, coming 749 * from the Front (direct request to tomcat, bypassing any HTTP server if any). 750 */ 751 protected class FrontFromFrontStatsEntry 752 { 753 /** front site name */ 754 protected String _frontSite; 755 /** front path */ 756 protected String _frontPath; 757 /** is front cacheable */ 758 protected boolean _frontCacheable; 759 /** front hits */ 760 protected int _frontHits; 761 /** front cache hits 1 */ 762 protected int _frontCacheHits1; 763 /** front cache hits 2 */ 764 protected int _frontCacheHits2; 765 766 /** 767 * Ctor 768 * @param data map of raw data object. 769 */ 770 protected FrontFromFrontStatsEntry(Map<String, Object> data) 771 { 772 _frontSite = (String) data.get("FRONT_SITE"); 773 _frontPath = (String) data.get("FRONT_PATH"); 774 _frontCacheable = (boolean) data.get("FRONT_CACHEABLE"); 775 _frontHits = (int) data.get("FRONT_HITS"); 776 _frontCacheHits1 = (int) data.get("FRONT_CACHE_HITS_1"); 777 _frontCacheHits2 = (int) data.get("FRONT_CACHE_HITS_2"); 778 } 779 } 780 781 /** 782 * Object model representing an entry of stats for a back resource (ie. a 783 * page). 784 */ 785 protected class BackStatsEntry 786 { 787 /** page id */ 788 protected final String _pageId; 789 /** page path */ 790 protected final String _pagePath; 791 /** cacheable */ 792 protected final boolean _cacheable; 793 /** hits */ 794 protected final int _hits; 795 796 /** 797 * Ctor 798 * @param data map of raw data object. 799 */ 800 protected BackStatsEntry(Map<String, Object> data) 801 { 802 _pageId = (String) data.get("PAGE_ID"); 803 _pagePath = (String) data.get("PAGE_PATH"); 804 _cacheable = (boolean) data.get("CACHEABLE"); 805 _hits = (int) data.get("HITS"); 806 } 807 } 808}