Index: main/plugin-web/scripts/mysql/jdbc_cache_mon.sql
===================================================================
--- main/plugin-web/scripts/mysql/jdbc_cache_mon.sql (revision 0)
+++ main/plugin-web/scripts/mysql/jdbc_cache_mon.sql (revision 0)
@@ -0,0 +1,123 @@
+--
+-- Copyright 2012 Anyware Services
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+drop table if exists Server_Requests;
+CREATE TABLE Server_Requests (
+ Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment,
+ Unique_Id VARCHAR(31) UNIQUE KEY,
+ Site VARCHAR(255) NOT NULL,
+ Remote_Host_Name VARCHAR(255),
+ Request_Date DATETIME NOT NULL,
+ Method VARCHAR(15) NOT NULL,
+ Path VARCHAR(511) NOT NULL,
+ Query_String VARCHAR(511) NOT NULL,
+ Protocol VARCHAR(15),
+ Ori_Status_Code CHAR(3) NOT NULL,
+ Ret_Status_Code CHAR(3) NOT NULL,
+ Cache_Hit BOOLEAN NOT NULL,
+ Referer VARCHAR(511),
+ User_Agent VARCHAR(511),
+ Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ Processed BOOLEAN NOT NULL DEFAULT 0
+) ENGINE=InnoDB;
+
+
+drop table if exists Front_Requests;
+CREATE TABLE Front_Requests (
+ Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment,
+ Unique_Id VARCHAR(31),
+ Internal_Uuid VARCHAR(63),
+ Site VARCHAR(255),
+ Ametys_Path VARCHAR(511) NOT NULL,
+ Cacheable BOOLEAN NOT NULL,
+ Cache_Hit_1 BOOLEAN,
+ Cache_Hit_2 BOOLEAN,
+ Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ Processed BOOLEAN NOT NULL DEFAULT 0
+) ENGINE=InnoDB;
+
+drop table if exists Back_Requests;
+CREATE TABLE Back_Requests (
+ Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment,
+ Internal_Uuid VARCHAR(63),
+ Page_Id VARCHAR(63) NOT NULL,
+ Page_Path VARCHAR(63) NOT NULL,
+ Rendering_Context VARCHAR(15) NOT NULL,
+ Workspace_JCR VARCHAR(15) NOT NULL,
+ Cacheable BOOLEAN NOT NULL,
+ Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ Processed BOOLEAN NOT NULL DEFAULT 0
+) ENGINE=InnoDB;
+
+drop table if exists Back_Page_Element_Requests;
+CREATE TABLE Back_Page_Element_Requests (
+ Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment,
+ Internal_Uuid VARCHAR(63),
+ Page_Element_Id VARCHAR(63) NOT NULL,
+ Page_Element_Type VARCHAR(31) NOT NULL,
+ Page_Id VARCHAR(63) NOT NULL,
+ Rendering_Context VARCHAR(15) NOT NULL,
+ Workspace_JCR VARCHAR(15) NOT NULL,
+ Cacheable BOOLEAN NOT NULL,
+ Cache_Hit BOOLEAN,
+ Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ Processed BOOLEAN NOT NULL DEFAULT 0
+) ENGINE=InnoDB;
+
+drop table if exists Cache_Front_Stats;
+CREATE TABLE Cache_Front_Stats (
+ Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment,
+ Server_Site VARCHAR(255),
+ Server_Path VARCHAR(511),
+ Server_Hits INT UNSIGNED NOT NULL DEFAULT 0,
+ Server_Cache_Hits INT UNSIGNED NOT NULL DEFAULT 0,
+ Front_Site VARCHAR(255),
+ Front_Path VARCHAR(511),
+ Front_Cacheable BOOLEAN DEFAULT 0,
+ Front_Hits INT UNSIGNED NOT NULL DEFAULT 0,
+ Front_Cache_Hits_1 INT UNSIGNED NOT NULL DEFAULT 0,
+ Front_Cache_Hits_2 INT UNSIGNED NOT NULL DEFAULT 0,
+ Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ -- Updated_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
+
+drop table if exists Cache_Back_Stats;
+CREATE TABLE Cache_Back_Stats (
+ Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment,
+ Page_Id VARCHAR(63) NOT NULL,
+ Page_Path VARCHAR(63) NOT NULL,
+ Rendering_Context VARCHAR(15) NOT NULL,
+ Workspace_JCR VARCHAR(15) NOT NULL,
+ Cacheable BOOLEAN NOT NULL,
+ Hits INT UNSIGNED NOT NULL,
+ Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ -- Updated_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
+
+drop table if exists Cache_Back_Page_Element_Stats;
+CREATE TABLE Cache_Back_Page_Element_Stats (
+ Id BIGINT UNSIGNED PRIMARY KEY NOT NULL auto_increment,
+ Page_Element_Id VARCHAR(63) NOT NULL,
+ Page_Id VARCHAR(63) NOT NULL,
+ Rendering_Context VARCHAR(15) NOT NULL,
+ Workspace_JCR VARCHAR(15) NOT NULL,
+ Cacheable BOOLEAN NOT NULL,
+ Hits INT UNSIGNED NOT NULL,
+ Cache_Hits INT UNSIGNED NOT NULL,
+ Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ -- Updated_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
+
Index: main/plugin-web/i18n/messages_en.xml
===================================================================
--- main/plugin-web/i18n/messages_en.xml (revision 20474)
+++ main/plugin-web/i18n/messages_en.xml (working copy)
@@ -290,6 +290,20 @@
You don't have an account?
+ Cache Monitoring
+ Monitoring database connection
+ Driver
+ JDBC Driver fully qualified class name
+ Url
+ JDBC URL for connecting to the database
+ User
+ User name for connecting to the database
+ Password
+ Password associated with the user name for connecting to the database
+
+
General information
@@ -326,6 +340,8 @@
Configure
Site statistics
Global statistics
+ Cache back statistics
+ Cache front statistics
Delete
Open website as a contributor
New site
@@ -449,7 +465,47 @@
Content count in all the sites
Resource explorer
Pages
-
+ Loading statistics, please wait...
+ Actions
+ Reinitialize the statistics
+ Quit
+ Help
+ Statistics of the caches for the site {name} (back)
+ Reload
+ This screen displays some statistics about pages and page elements on the back-office.<br/><br/> \
+ Statistics can be filtered by context (couple rendering context - workspace JCR).<br/>Thus, it is possible to select or deselect contexts and then reload the table by clicking on the 'reload' bouton.<br/><br/> \
+ Statistics columns are grouped by category ('page' or 'page element').<br/>The 'cacheable' column represent the rate of page elements that are cacheable, weighted by element hits.<br/>This way, the rates 'cacheable' and 'efficiency' can be directly compared.
+ Pages
+ Page elements
+ Title
+ Hits
+ Cacheable
+ Efficiency
+ Hits
+ Cache hits
+ Statistics of the caches for the site {name} (front)
+ This screen displays statistics of the caches use on the front-office.<br/><br/> \
+ It is possible to get more information by clicking on the appropriate button in the action menu.<br/>You will get the detailed statistics for Apache and the front-offiche.<br/><br/> \
+ As it is for the back-office statistics, the 'cacheable' rate is weighted by the number of hits.
+ Display/Hide details
+ Main
+ Efficiency
+ Hits
+ Cache hits
+ Back
+ Titre
+ Cacheable
+ Total
+ Apache
+ Front
+ Total
+ Apache
+ Front
+ Total
+ Apache
+ Front
+ Hits
+
@@ -457,6 +513,7 @@
Skins management
Skins management
Actions
+
Import a Ametys skin
Import a Ametys skin from a ZIP file
Export to ZIP format
Index: main/plugin-web/i18n/messages_fr.xml
===================================================================
--- main/plugin-web/i18n/messages_fr.xml (revision 20474)
+++ main/plugin-web/i18n/messages_fr.xml (working copy)
@@ -290,6 +290,20 @@
Vous n'avez pas de compte ?
+ Monitoring Cache
+ Connexion à la base de données de monitoring
+ Pilote
+ Nom complet de la classe driver JDBC à charger
+ Url
+ Url JDBC de connexion au serveur de base de données
+ Utilisateur
+ Nom d'utilisateur à utiliser lors de la connexion au serveur de base de données
+ Mot de passe
+ Mot de passe associé au nom d'utilisateur à utiliser lors de la connexion au serveur de base de données
+
+
Informations générales
@@ -326,6 +340,8 @@
Configurer
Statistiques du site
Statistiques globales
+ Statistiques du cache (back)
+ Statistiques du cache (front)
Supprimer
Ouvrir dans le CMS
Nouveau site
@@ -449,6 +465,46 @@
Nombre de contenus dans l'ensemble des sites
Explorateur de ressources
Pages
+ Chargement des statistiques, veuillez patienter...
+ Actions
+ Réinitialiser les stats
+ Quitter
+ Aide
+ Statistiques des caches pour le site {name} (back)
+ Recharger
+ Ce tableau centralise les statistiques d'accès aux pages et éléments des pages sur le back-office.<br/><br/> \
+ Les statistiques peuvent être filtrées par contexte (couple contexte de rendu - workspace JCR).<br/>Ainsi il est possible de sélectionner ou désélectionner des contextes puis de recharger le tableau à l'aide du bouton 'recharger'.<br/><br/> \
+ Les statistiques sont regroupées selon qu'elles se rapportent aux pages ou aux éléments des pages.<br/>La colonne 'cacheable' représente le taux d'éléments des pages qui sont cacheables, pondéré par le nombre de hits par élément.<br/>En conséquence, les taux 'cacheable' et 'efficacité' peuvent être comparés directement.
+ Pages
+ Eléments des pages
+ Titre
+ Hits
+ Cacheable
+ Efficacité
+ Hits
+ Cache hits
+ Statistiques des caches pour le site {name} (front)
+ Ce tableau centralise les statistiques d'utilisation des caches côté front-office.<br/><br/> \
+ Il est possible d'afficher plus d'informations en cliquant sur le bouton approprié dans le menu des actions.<br/>Vous obtiendrez le détails des statistiques pour Apache et le front-office.<br/><br/> \
+ Comme pour les statistique côté back-office, le taux 'cacheable' est pondéré par le nombre de hits.
+ Afficher/Masquer détails
+ Général
+ Efficacité
+ Hits
+ Cache hits
+ Back
+ Titre
+ Cacheable
+ Total
+ Apache
+ Front
+ Total
+ Apache
+ Front
+ Total
+ Apache
+ Front
+ Hits
+
+
+
+
+
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_DRIVER_DESCRIPTION
+
+
+
+ com.mysql.jdbc.Driver
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP
+ 10
+
+
+
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_URL_DESCRIPTION
+
+
+
+ jdbc:mysql://servername/basename
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP
+ 20
+
+
+
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_USER_DESCRIPTION
+ username
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP
+ 30
+
+
+
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_PASSWORD_DESCRIPTION
+ password
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_CATEGORY
+ PLUGINS_WEB_CONFIG_CACHE_MONITORING_DATASOURCE_JDBC_GROUP
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: main/plugin-web/pages/administrator/sites/cache-statistics-back-tree.xsl
===================================================================
--- main/plugin-web/pages/administrator/sites/cache-statistics-back-tree.xsl (revision 0)
+++ main/plugin-web/pages/administrator/sites/cache-statistics-back-tree.xsl (revision 0)
@@ -0,0 +1,232 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+
+
+
+
+
Index: main/plugin-web/pages/administrator/sites/cache-statistics-back.xsl
===================================================================
--- main/plugin-web/pages/administrator/sites/cache-statistics-back.xsl (revision 0)
+++ main/plugin-web/pages/administrator/sites/cache-statistics-back.xsl (revision 0)
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: main/plugin-web/pages/administrator/sites/cache-statistics-servers-tree.xsl
===================================================================
--- main/plugin-web/pages/administrator/sites/cache-statistics-servers-tree.xsl (revision 0)
+++ main/plugin-web/pages/administrator/sites/cache-statistics-servers-tree.xsl (revision 0)
@@ -0,0 +1,435 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ N/A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ N/A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: main/plugin-web/pages/administrator/sites/cache-statistics-servers.xsl
===================================================================
--- main/plugin-web/pages/administrator/sites/cache-statistics-servers.xsl (revision 0)
+++ main/plugin-web/pages/administrator/sites/cache-statistics-servers.xsl (revision 0)
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/server/ApacheOnlyCacheStats.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/server/ApacheOnlyCacheStats.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/server/ApacheOnlyCacheStats.java (revision 0)
@@ -0,0 +1,74 @@
+package org.ametys.web.cachemonitoring.server;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.commons.lang.BooleanUtils;
+
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats;
+
+/**
+ * Apache cache stats objects
+ */
+public class ApacheOnlyCacheStats implements ResourceCacheStats
+{
+ private final String _site;
+ private final String _path;
+ private final boolean _cacheHit;
+ private final int _newHits;
+
+ /**
+ * Ctor
+ * @param data
+ */
+ public ApacheOnlyCacheStats(Map data)
+ {
+ _site = (String) data.get("Site");
+ _path = (String) data.get("Path");
+ _cacheHit = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit")); // converts null to false, avoiding NPE while unboxing.
+ _newHits = ((Long) data.get("increment")).intValue();
+ }
+
+ @Override
+ public ResourceCacheStatsType getType()
+ {
+ return ResourceCacheStatsType.APACHE_ONLY;
+ }
+
+ @Override
+ public void configureSqlFind(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _site);
+ ps.setString(2, _path);
+ }
+
+ @Override
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _site);
+ ps.setString(2, _path);
+ ps.setInt(3, _newHits);
+ ps.setInt(4, getCacheHits());
+ }
+
+ @Override
+ public void configureSqlUpdate(PreparedStatement ps) throws SQLException
+ {
+ ps.setInt(1, _newHits);
+ ps.setInt(2, getCacheHits());
+ ps.setString(3, _site);
+ ps.setString(4, _path);
+ }
+
+ @Override
+ public int getHits()
+ {
+ return _newHits;
+ }
+
+ private int getCacheHits()
+ {
+ return _cacheHit ? _newHits : 0;
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontOnlyCacheStats.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontOnlyCacheStats.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontOnlyCacheStats.java (revision 0)
@@ -0,0 +1,88 @@
+package org.ametys.web.cachemonitoring.front;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.commons.lang.BooleanUtils;
+
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats;
+
+/**
+ * Front (without apache) cache stats objects
+ */
+public class FrontOnlyCacheStats implements ResourceCacheStats
+{
+ private final String _site;
+ private final String _path;
+ private final boolean _cacheable;
+ private final boolean _cacheHit1;
+ private final boolean _cacheHit2;
+ private final int _newHits;
+
+ /**
+ * Ctor
+ * @param data
+ */
+ public FrontOnlyCacheStats(Map data)
+ {
+ _site = (String) data.get("Site");
+ _path = (String) data.get("Ametys_Path");
+ _cacheable = (Boolean) data.get("Cacheable");
+ _cacheHit1 = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit_1")); // converts null to false, avoiding NPE while unboxing.
+ _cacheHit2 = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit_2")); // converts null to false, avoiding NPE while unboxing.
+ _newHits = ((Long) data.get("increment")).intValue();
+ }
+
+ @Override
+ public ResourceCacheStatsType getType()
+ {
+ return ResourceCacheStatsType.FRONT_ONLY;
+ }
+
+ @Override
+ public void configureSqlFind(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _site);
+ ps.setString(2, _path);
+ }
+
+ @Override
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _site);
+ ps.setString(2, _path);
+ ps.setBoolean(3, _cacheable);
+ ps.setInt(4, _newHits);
+ ps.setInt(5, getCacheHits1());
+ ps.setInt(6, getCacheHits2());
+ }
+
+
+ @Override
+ public void configureSqlUpdate(PreparedStatement ps) throws SQLException
+ {
+ ps.setBoolean(1, _cacheable);
+ ps.setInt(2, _newHits);
+ ps.setInt(3, getCacheHits1());
+ ps.setInt(4, getCacheHits2());
+ ps.setString(5, _site);
+ ps.setString(6, _path);
+ }
+
+ @Override
+ public int getHits()
+ {
+ return _newHits;
+ }
+
+ private int getCacheHits1()
+ {
+ return _cacheHit1 ? _newHits : 0;
+ }
+
+ private int getCacheHits2()
+ {
+ return _cacheHit2 ? _newHits : 0;
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontFromApacheCacheStats.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontFromApacheCacheStats.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/front/FrontFromApacheCacheStats.java (revision 0)
@@ -0,0 +1,113 @@
+package org.ametys.web.cachemonitoring.front;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.commons.lang.BooleanUtils;
+
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats;
+
+/**
+ * Front from apache cache stats objects
+ */
+public class FrontFromApacheCacheStats implements ResourceCacheStats
+{
+ private final String _apacheSite;
+ private final String _apachePath;
+ private final boolean _apacheCacheHit;
+
+ private final String _frontSite;
+ private final String _frontPath;
+ private final boolean _frontCacheable;
+ private final boolean _frontCacheHit1;
+ private final boolean _frontCacheHit2;
+
+ private final int _newHits;
+
+ /**
+ * Ctor
+ * @param data
+ */
+ public FrontFromApacheCacheStats(Map data)
+ {
+ _apacheSite = (String) data.get("S_Site");
+ _apachePath = (String) data.get("S_Path");
+ _apacheCacheHit = BooleanUtils.toBoolean((Boolean) data.get("S_Cache_Hit")); // converts null to false, avoiding NPE while unboxing.
+
+ _frontSite = (String) data.get("F_Site");
+ _frontPath = (String) data.get("F_Ametys_Path");
+ _frontCacheable = (Boolean) data.get("F_Cacheable");
+ _frontCacheHit1 = BooleanUtils.toBoolean((Boolean) data.get("F_Cache_Hit_1")); // converts null to false, avoiding NPE while unboxing.
+ _frontCacheHit2 = BooleanUtils.toBoolean((Boolean) data.get("F_Cache_Hit_2")); // converts null to false, avoiding NPE while unboxing.
+
+ _newHits = ((Long) data.get("increment")).intValue();
+ }
+
+ @Override
+ public ResourceCacheStatsType getType()
+ {
+ return ResourceCacheStatsType.FRONT_FROM_APACHE;
+ }
+
+ @Override
+ public void configureSqlFind(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _apacheSite);
+ ps.setString(2, _apachePath);
+ }
+
+ @Override
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _apacheSite);
+ ps.setString(2, _apachePath);
+ ps.setInt(3, _newHits);
+ ps.setInt(4, getServerCacheHits());
+
+ ps.setString(5, _frontSite);
+ ps.setString(6, _frontPath);
+ ps.setBoolean(7, _frontCacheable);
+ ps.setInt(8, _newHits);
+ ps.setInt(9, getFrontCacheHits1());
+ ps.setInt(10, getFrontCacheHits2());
+ }
+
+ @Override
+ public void configureSqlUpdate(PreparedStatement ps) throws SQLException
+ {
+ ps.setInt(1, _newHits);
+ ps.setInt(2, getServerCacheHits());
+
+ ps.setString(3, _frontSite);
+ ps.setString(4, _frontPath);
+ ps.setBoolean(5, _frontCacheable);
+ ps.setInt(6, _newHits);
+ ps.setInt(7, getFrontCacheHits1());
+ ps.setInt(8, getFrontCacheHits2());
+
+ ps.setString(9, _apacheSite);
+ ps.setString(10, _apachePath);
+ }
+
+ @Override
+ public int getHits()
+ {
+ return _newHits;
+ }
+
+ private int getServerCacheHits()
+ {
+ return _apacheCacheHit ? _newHits : 0;
+ }
+
+ private int getFrontCacheHits1()
+ {
+ return _frontCacheHit1 ? _newHits : 0;
+ }
+
+ private int getFrontCacheHits2()
+ {
+ return _frontCacheHit2 ? _newHits : 0;
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceAccess.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceAccess.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceAccess.java (revision 0)
@@ -0,0 +1,91 @@
+package org.ametys.web.cachemonitoring.back;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import org.apache.commons.lang.StringUtils;
+
+import org.ametys.web.cachemonitoring.back.PageElementAccess.PageElementType;
+import org.ametys.web.cachemonitoring.core.ResourceAccess;
+import org.ametys.web.renderingcontext.RenderingContext;
+
+/**
+ * Page resource access. Represent an access to a page on the back-office.
+ */
+public class PageResourceAccess implements ResourceAccess
+{
+ private final String _internalUuid;
+ private final String _pageID;
+ private final String _path;
+ private RenderingContext _renderingContext;
+ private String _workspaceJCR;
+ private boolean _cacheable;
+
+ /**
+ * Ctor
+ * @param internalUuid
+ * @param pageID
+ * @param path
+ */
+ public PageResourceAccess(String internalUuid, String pageID, String path)
+ {
+ _internalUuid = internalUuid;
+ _pageID = pageID;
+ _path = StringUtils.substringBefore(path, "?");
+ }
+
+ /**
+ * Create a page element resource access for this page resource access.
+ * @param pageElementID
+ * @param pageElementType
+ * @return the new PageElementAccess
+ */
+ public PageElementAccess createPageElementAccess(String pageElementID, PageElementType pageElementType)
+ {
+ return PageElementAccess.createPageElementAccess(_internalUuid, _pageID, pageElementID, pageElementType, _renderingContext, _workspaceJCR);
+ }
+
+ /**
+ * Set the rendering context
+ * @param renderingContext
+ */
+ public void setRenderingContext(RenderingContext renderingContext)
+ {
+ _renderingContext = renderingContext;
+ }
+
+ /**
+ * Set the jcr workspace
+ * @param workspaceJCR
+ */
+ public void setWorkspaceJCR(String workspaceJCR)
+ {
+ _workspaceJCR = workspaceJCR;
+ }
+
+ /**
+ * Set the resource as cacheable or not.
+ * @param cacheable
+ */
+ public void setCacheable(boolean cacheable)
+ {
+ _cacheable = cacheable;
+ }
+
+ @Override
+ public MonitoredResourceType getType()
+ {
+ return MonitoredResourceType.BACK_PAGE_RESOURCE;
+ }
+
+ @Override
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _internalUuid);
+ ps.setString(2, _pageID);
+ ps.setString(3, _path);
+ ps.setString(4, _renderingContext.toString());
+ ps.setString(5, _workspaceJCR);
+ ps.setBoolean(6, _cacheable);
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementAccess.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementAccess.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementAccess.java (revision 0)
@@ -0,0 +1,121 @@
+package org.ametys.web.cachemonitoring.back;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import org.ametys.web.cachemonitoring.core.ResourceAccess;
+import org.ametys.web.renderingcontext.RenderingContext;
+import org.ametys.web.repository.page.ZoneItem.ZoneType;
+
+/**
+ * Page element resource access. Represent an access to a element of a page on the back-office.
+ */
+public class PageElementAccess implements ResourceAccess
+{
+ /** Type of a PageElementAccess. */
+ public enum PageElementType
+ {
+ /** A service */
+ SERVICE,
+ /** A content */
+ CONTENT,
+ /** An inputdata */
+ INPUTDATA;
+
+ @Override
+ public String toString()
+ {
+ return super.name().toLowerCase();
+ }
+
+ /**
+ * Utility method the get the PageElementType corresponding to a ZoneType
+ * @param ziType
+ * @return the PageElementType
+ */
+ public static PageElementType fromZoneItemType(ZoneType ziType)
+ {
+ switch (ziType)
+ {
+ case CONTENT:
+ return CONTENT;
+ case SERVICE:
+ return SERVICE;
+ default:
+ throw new IllegalArgumentException("Unknown ZoneType : " + ziType);
+ }
+ }
+ }
+
+ private final String _internalUuid;
+ private final String _pageElementID;
+ private final PageElementType _pageElementType;
+ private final String _pageID;
+ private final RenderingContext _renderingContext;
+ private final String _workspaceJCR;
+ private boolean _cacheable;
+ private Boolean _cacheHit;
+
+ /**
+ * Ctor
+ * @param uuid
+ * @param peid
+ * @param pet
+ * @param pageID
+ * @param rc
+ * @param ws
+ */
+ protected PageElementAccess(String uuid, String peid, PageElementType pet, String pageID, RenderingContext rc, String ws)
+ {
+ _internalUuid = uuid;
+ _pageElementID = peid;
+ _pageElementType = pet;
+ _pageID = pageID;
+ _renderingContext = rc;
+ _workspaceJCR = ws;
+ }
+
+ static PageElementAccess createPageElementAccess(String uuid, String pageID, String pageElementID, PageElementType pageElementType, RenderingContext rc, String ws)
+ {
+ return new PageElementAccess(uuid, pageElementID, pageElementType, pageID, rc, ws);
+ }
+
+ /**
+ * Set the resource as cacheable or not.
+ * @param cacheable
+ */
+ public void setCacheable(boolean cacheable)
+ {
+ _cacheable = cacheable;
+ }
+
+ /**
+ * Set the cache hit to true/false
+ * @param hit
+ */
+ public void setCacheHit(boolean hit)
+ {
+ _cacheHit = hit;
+ }
+
+ @Override
+ public MonitoredResourceType getType()
+ {
+ return MonitoredResourceType.BACK_PAGE_ELEMENT;
+ }
+
+ @Override
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _internalUuid);
+ ps.setString(2, _pageElementID);
+ ps.setString(3, _pageElementType.toString());
+ ps.setString(4, _pageID);
+ ps.setString(5, _renderingContext.toString());
+ ps.setString(6, _workspaceJCR);
+ ps.setBoolean(7, _cacheable);
+ // Handle null cases, avoid NPE and set NULL into the db in thoses cases.
+ ps.setObject(8, _cacheHit, Types.BOOLEAN);
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementCacheStats.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementCacheStats.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageElementCacheStats.java (revision 0)
@@ -0,0 +1,89 @@
+package org.ametys.web.cachemonitoring.back;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.commons.lang.BooleanUtils;
+
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats;
+
+/**
+ * Page element cache stats objects
+ */
+public class PageElementCacheStats implements ResourceCacheStats
+{
+ private final String _pageElementId;
+ private final String _pageId;
+ private final String _renderingContext;
+ private final String _workspaceJCR;
+ private final boolean _cacheable;
+ private final boolean _cacheHit;
+ private final int _newHits;
+
+ /**
+ * Ctor
+ * @param data
+ */
+ public PageElementCacheStats(Map data)
+ {
+ _pageElementId = (String) data.get("Page_Element_Id");
+ _pageId = (String) data.get("Page_Id");
+ _renderingContext = (String) data.get("Rendering_Context");
+ _workspaceJCR = (String) data.get("Workspace_JCR");
+ _cacheable = (Boolean) data.get("Cacheable");
+ _cacheHit = BooleanUtils.toBoolean((Boolean) data.get("Cache_Hit")); // converts null to false, avoiding NPE while unboxing.
+ _newHits = ((Long) data.get("increment")).intValue();
+ }
+
+ @Override
+ public ResourceCacheStatsType getType()
+ {
+ return ResourceCacheStatsType.BACK_PAGE_ELEMENT;
+ }
+
+ @Override
+ public void configureSqlFind(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _pageElementId);
+ ps.setString(2, _pageId);
+ ps.setString(3, _renderingContext);
+ ps.setString(4, _workspaceJCR);
+ }
+
+ @Override
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _pageElementId);
+ ps.setString(2, _pageId);
+ ps.setString(3, _renderingContext);
+ ps.setString(4, _workspaceJCR);
+ ps.setBoolean(5, _cacheable);
+ ps.setInt(6, _newHits);
+ ps.setInt(7, getCacheHits());
+ }
+
+ @Override
+ public void configureSqlUpdate(PreparedStatement ps) throws SQLException
+ {
+ ps.setBoolean(1, _cacheable);
+ ps.setInt(2, _newHits);
+ ps.setInt(3, getCacheHits());
+
+ ps.setString(4, _pageElementId);
+ ps.setString(5, _pageId);
+ ps.setString(6, _renderingContext);
+ ps.setString(7, _workspaceJCR);
+ }
+
+ @Override
+ public int getHits()
+ {
+ return _newHits;
+ }
+
+ private int getCacheHits()
+ {
+ return _cacheHit ? _newHits : 0;
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceCacheStats.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceCacheStats.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/back/PageResourceCacheStats.java (revision 0)
@@ -0,0 +1,77 @@
+package org.ametys.web.cachemonitoring.back;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats;
+
+/**
+ * Page cache stats objects
+ */
+public class PageResourceCacheStats implements ResourceCacheStats
+{
+ private final String _pageId;
+ private final String _path;
+ private final String _renderingContext;
+ private final String _workspaceJCR;
+ private final boolean _cacheable;
+ private final int _newHits;
+
+ /**
+ * Ctor
+ * @param data
+ */
+ public PageResourceCacheStats(Map data)
+ {
+ _pageId = (String) data.get("Page_Id");
+ _path = (String) data.get("Page_Path");
+ _renderingContext = (String) data.get("Rendering_Context");
+ _workspaceJCR = (String) data.get("Workspace_JCR");
+ _cacheable = (Boolean) data.get("Cacheable");
+ _newHits = ((Long) data.get("increment")).intValue();
+ }
+
+ @Override
+ public ResourceCacheStatsType getType()
+ {
+ return ResourceCacheStatsType.BACK_PAGE;
+ }
+
+ @Override
+ public void configureSqlFind(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _pageId);
+ ps.setString(2, _renderingContext);
+ ps.setString(3, _workspaceJCR);
+ }
+
+ @Override
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _pageId);
+ ps.setString(2, _path);
+ ps.setString(3, _renderingContext);
+ ps.setString(4, _workspaceJCR);
+ ps.setBoolean(5, _cacheable);
+ ps.setInt(6, _newHits);
+ }
+
+ @Override
+ public void configureSqlUpdate(PreparedStatement ps) throws SQLException
+ {
+ ps.setString(1, _path);
+ ps.setBoolean(2, _cacheable);
+ ps.setInt(3, _newHits);
+
+ ps.setString(4, _pageId);
+ ps.setString(5, _renderingContext);
+ ps.setString(6, _workspaceJCR);
+ }
+
+ @Override
+ public int getHits()
+ {
+ return _newHits;
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/generators/PageElementCacheStatsGenerator.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/generators/PageElementCacheStatsGenerator.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/generators/PageElementCacheStatsGenerator.java (revision 0)
@@ -0,0 +1,527 @@
+package org.ametys.web.cachemonitoring.generators;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.ProcessingException;
+import org.apache.cocoon.generation.ServiceableGenerator;
+import org.apache.cocoon.xml.AttributesImpl;
+import org.apache.cocoon.xml.XMLUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.joda.time.Duration;
+import org.joda.time.format.PeriodFormat;
+import org.xml.sax.SAXException;
+
+import org.ametys.cms.repository.Content;
+import org.ametys.plugins.repository.AmetysObjectIterable;
+import org.ametys.plugins.repository.AmetysRepositoryException;
+import org.ametys.plugins.repository.UnknownAmetysObjectException;
+import org.ametys.runtime.datasource.ConnectionHelper;
+import org.ametys.web.cachemonitoring.core.CacheMonitoringSqlStatements;
+import org.ametys.web.inputdata.InputDataExtensionPoint;
+import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.page.Zone;
+import org.ametys.web.repository.page.ZoneItem;
+import org.ametys.web.repository.page.ZoneItem.ZoneType;
+import org.ametys.web.repository.site.Site;
+import org.ametys.web.repository.site.SiteManager;
+import org.ametys.web.repository.sitemap.Sitemap;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Cache stats generator for related to the back-office (pages and page
+ * elements)
+ */
+public class PageElementCacheStatsGenerator extends ServiceableGenerator
+{
+ /** Input Data extension point */
+ protected InputDataExtensionPoint _inputDataExt;
+
+ /** Ametys resolver */
+ protected SiteManager _siteManager;
+
+ /** Map linking page Id to page stats entries */
+ protected Multimap _pageIdMap;
+
+ /** Map of stats entries */
+ protected Multimap _statsMap;
+
+ @Override
+ public void service(ServiceManager sm) throws ServiceException
+ {
+ super.service(sm);
+ _inputDataExt = (InputDataExtensionPoint) sm.lookup(InputDataExtensionPoint.ROLE);
+ _siteManager = (SiteManager) sm.lookup(SiteManager.ROLE);
+ }
+
+ @Override
+ public void recycle()
+ {
+ _statsMap = null;
+ _pageIdMap = null;
+
+ super.recycle();
+ }
+
+ @Override
+ public void generate() throws IOException, SAXException, ProcessingException
+ {
+ String siteName = parameters.getParameter("siteName", "");
+ Site site = null;
+
+ try
+ {
+ site = _siteManager.getSite(siteName);
+ }
+ catch (UnknownAmetysObjectException e)
+ {
+ String message = "The site '" + siteName + "' does not exist.";
+ getLogger().error(message, e);
+ throw new ProcessingException(message, e);
+ }
+
+ _pageIdMap = HashMultimap.create(); // does not allow duplicate key-value entries.
+ _statsMap = LinkedListMultimap.create(); // can contain non-distinct key-value pairs.
+
+ _initializePECacheStats();
+
+ List contexts = _getContextsFilter(parameters.getParameter("contexts", null));
+
+ Long start = null;
+ if (getLogger().isDebugEnabled())
+ {
+ start = System.currentTimeMillis();
+
+ getLogger().debug("Starting to SAX back-office cache statistics.");
+ }
+
+ contentHandler.startDocument();
+ XMLUtils.startElement(contentHandler, "stats");
+
+ // sax info for the given site.
+ _saxStats(site, contexts);
+
+ XMLUtils.endElement(contentHandler, "stats");
+ contentHandler.endDocument();
+
+ if (getLogger().isDebugEnabled())
+ {
+ if (start != null)
+ {
+ long end = System.currentTimeMillis();
+ String duration = PeriodFormat.getDefault().print(new Duration(end - start).toPeriod());
+ getLogger().debug(String.format("The SAX process of the back-office cache statistics took %s", duration));
+ }
+ }
+ }
+
+ /**
+ * Initialize statistics by retrieving data from the monitoring database.
+ * @throws ProcessingException
+ */
+ private void _initializePECacheStats() throws ProcessingException
+ {
+ Connection conn = null;
+ Statement st = null;
+ ResultSet rs = null;
+
+ try
+ {
+ conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID);
+
+ String sql = _getPECacheStatsQuery();
+ st = conn.createStatement();
+ rs = st.executeQuery(sql);
+
+ _processCacheStatsResultSet(rs);
+ }
+ catch (SQLException e)
+ {
+ String msg = "SQL Error while retrieving page element cache stats";
+ getLogger().error(msg);
+ throw new ProcessingException(msg, e);
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ ConnectionHelper.cleanup(st);
+ ConnectionHelper.cleanup(conn);
+ }
+ }
+
+ private static String _getPECacheStatsQuery()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(" SELECT p.Page_Id, p.Rendering_Context, p.Workspace_JCR, p.Cacheable AS P_Cacheable, p.Hits AS P_Hits,");
+ sb.append(" pe.Page_Element_Id, pe.Cacheable AS PE_Cacheable, pe.Hits AS PE_Hits, pe.Cache_Hits AS PE_Cache_Hits");
+ sb.append(" FROM CACHE_BACK_STATS p");
+ sb.append(" LEFT OUTER JOIN Cache_Back_Page_Element_Stats pe");
+ sb.append(" ON p.Page_Id = pe.Page_Id AND p.Rendering_Context = pe.Rendering_Context AND p.Workspace_JCR = pe.Workspace_JCR");
+
+ return sb.toString();
+ }
+
+ private void _processCacheStatsResultSet(ResultSet rs) throws SQLException
+ {
+ ResultSetMetaData meta = rs.getMetaData();
+ String[] cols = new String[meta.getColumnCount()];
+
+ for (int i = 0; i < cols.length; i++)
+ {
+ cols[i] = meta.getColumnLabel(i + 1);
+ }
+
+ while (rs.next())
+ {
+ Map data = new HashMap();
+ for (int i = 0; i < cols.length; i++)
+ {
+ data.put(cols[i], rs.getObject(i + 1));
+ }
+
+ PageStatsEntry pageStatsEntry = new PageStatsEntry(data);
+ PageElementStatsEntry pageElementStatsEntry = new PageElementStatsEntry(data);
+
+ _pageIdMap.put(pageStatsEntry._pageId, pageStatsEntry);
+ _statsMap.put(pageStatsEntry, pageElementStatsEntry);
+ }
+ }
+
+ /**
+ * Analyse the 'contexts' parameter to filter the data to sax depending on requested contexts.
+ * A context is a couple as follows : 'renderingContext' - 'workspaceJcr'
+ * @param parameter
+ */
+ private List _getContextsFilter(String contexts)
+ {
+ if (StringUtils.isEmpty(contexts))
+ {
+ return null;
+ }
+
+ return Arrays.asList(StringUtils.split(contexts, '#'));
+ }
+
+ private void _saxStats(Site site, List contexts) throws SAXException
+ {
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("id", site.getId());
+ attrs.addCDATAAttribute("name", site.getName());
+ XMLUtils.startElement(contentHandler, "site", attrs);
+
+ for (Sitemap sitemap : site.getSitemaps())
+ {
+ _saxStats(sitemap, contexts);
+ }
+
+ XMLUtils.endElement(contentHandler, "site");
+ }
+
+ private void _saxStats(Sitemap sitemap, List contexts) throws SAXException
+ {
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("id", sitemap.getId());
+ attrs.addCDATAAttribute("name", sitemap.getName());
+ XMLUtils.startElement(contentHandler, "sitemap", attrs);
+
+ for (Page page : sitemap.getChildrenPages())
+ {
+ _saxPageStats(page, contexts);
+ }
+
+ XMLUtils.endElement(contentHandler, "sitemap");
+ }
+
+ private void _saxPageStats(Page page, List contexts) throws SAXException
+ {
+ String pageId = page.getId();
+
+ // SAX'ing page info
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("id", pageId);
+ attrs.addCDATAAttribute("title", page.getTitle());
+ attrs.addCDATAAttribute("name", page.getName());
+ attrs.addCDATAAttribute("path", page.getPathInSitemap());
+ XMLUtils.startElement(contentHandler, "page", attrs);
+
+ // SAX stats by contexts
+ if (_pageIdMap.containsKey(pageId))
+ {
+ List zones = new ArrayList();
+ for (Zone zone : page.getZones())
+ {
+ zones.add(zone);
+ }
+
+ _saxPageStatsContexts(page, zones, contexts);
+ }
+
+ for (Page child : page.getChildrenPages())
+ {
+ _saxPageStats(child, contexts);
+ }
+
+ XMLUtils.endElement(contentHandler, "page");
+ }
+
+ private void _saxPageStatsContexts(Page page, List zones, List contexts) throws SAXException
+ {
+ Collection pageStatsEntries = _pageIdMap.get(page.getId());
+
+ for (PageStatsEntry pageStats : pageStatsEntries)
+ {
+ // Filter out contexts that are not in the 'contexts' list.
+ // null lists implies that all contexts are allowed
+ if (contexts == null || contexts.contains(pageStats.getContext()))
+ {
+ _saxPageStatsContext(pageStats, zones);
+ }
+ }
+ }
+
+ private void _saxPageStatsContext(PageStatsEntry pageStats, List zones) throws SAXException
+ {
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("renderingContext", pageStats._renderingContext);
+ attrs.addCDATAAttribute("workspaceJCR", pageStats._workspaceJCR);
+ attrs.addCDATAAttribute("cacheable", String.valueOf(pageStats._cacheable));
+ XMLUtils.startElement(contentHandler, "context", attrs);
+
+ XMLUtils.createElement(contentHandler, "hits", String.valueOf(pageStats._hits));
+
+ Collection pageElementStats = _statsMap.get(pageStats);
+
+ // Iterate on existing zones
+ for (Zone zone : zones)
+ {
+ _saxZoneStats(zone, pageElementStats);
+ }
+
+ // Iterate on input data
+ _saxInputDataStats(pageElementStats);
+
+ XMLUtils.endElement(contentHandler, "context");
+ }
+
+ private void _saxZoneStats(Zone zone, Collection pageElementStats) throws SAXException
+ {
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("id", zone.getId());
+ attrs.addCDATAAttribute("name", zone.getName());
+ XMLUtils.startElement(contentHandler, "zone", attrs);
+
+ AmetysObjectIterable extends ZoneItem> zoneItems = zone.getZoneItems();
+ int index = 0;
+ for (ZoneItem zoneItem : zoneItems)
+ {
+ index++;
+ PageElementStatsEntry entry = _findPageElementStatsEntry(pageElementStats, zoneItem.getId());
+ if (entry != null)
+ {
+ _saxZoneItemStats(zoneItem, entry, index);
+ }
+ }
+
+ XMLUtils.endElement(contentHandler, "zone");
+ }
+
+
+ private void _saxZoneItemStats(ZoneItem zoneItem, PageElementStatsEntry entry, int index) throws SAXException
+ {
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("id", entry._pageElementID);
+ attrs.addCDATAAttribute("index", String.valueOf(index));
+ attrs.addCDATAAttribute("type", zoneItem.getType().toString().toLowerCase());
+ attrs.addCDATAAttribute("cacheable", String.valueOf(entry._cacheable));
+ _addZoneItemSaxInfo(attrs, zoneItem);
+ XMLUtils.startElement(contentHandler, "zoneitem", attrs);
+
+ XMLUtils.createElement(contentHandler, "hits", String.valueOf(entry._hits));
+ XMLUtils.createElement(contentHandler, "cacheHits", String.valueOf(entry._cacheHits));
+
+ XMLUtils.endElement(contentHandler, "zoneitem");
+ }
+
+ private void _addZoneItemSaxInfo(AttributesImpl attrs, ZoneItem zoneItem)
+ {
+ try
+ {
+ switch (zoneItem.getType())
+ {
+ case CONTENT:
+ Content content = zoneItem.getContent();
+ attrs.addCDATAAttribute("contentId", content.getId());
+ attrs.addCDATAAttribute("contentType", content.getType());
+ break;
+ case SERVICE:
+ attrs.addCDATAAttribute("serviceId", zoneItem.getServiceId());
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal zone item type. Allowed values are : " + Arrays.asList(ZoneType.values()));
+ }
+ }
+ catch (AmetysRepositoryException e)
+ {
+ // Ignore
+ }
+ }
+
+ private void _saxInputDataStats(Collection pageElementStats) throws SAXException
+ {
+ XMLUtils.startElement(contentHandler, "inputdata");
+
+ for (String id : _inputDataExt.getExtensionsIds())
+ {
+ PageElementStatsEntry entry = _findPageElementStatsEntry(pageElementStats, id);
+ _saxInputDataStatsEntry(entry, id);
+ }
+
+ XMLUtils.endElement(contentHandler, "inputdata");
+ }
+
+ private void _saxInputDataStatsEntry(PageElementStatsEntry entry, String inputDataId) throws SAXException
+ {
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("id", inputDataId);
+ attrs.addCDATAAttribute("title", inputDataId);
+ attrs.addCDATAAttribute("cacheable", String.valueOf(entry._cacheable));
+ XMLUtils.startElement(contentHandler, "inputdataitem", attrs);
+
+ XMLUtils.createElement(contentHandler, "hits", String.valueOf(entry._hits));
+ XMLUtils.createElement(contentHandler, "cacheHits", String.valueOf(entry._cacheHits));
+
+ XMLUtils.endElement(contentHandler, "inputdataitem");
+ }
+
+ private PageElementStatsEntry _findPageElementStatsEntry(Collection pageElementStats, String id)
+ {
+ for (PageElementStatsEntry entry : pageElementStats)
+ {
+ if (id.equals(entry._pageElementID))
+ {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Object model representing an entry of stats for a Page
+ */
+ @SuppressWarnings("javadoc")
+ protected class PageStatsEntry
+ {
+ protected final String _pageId;
+ protected final String _renderingContext;
+ protected final String _workspaceJCR;
+ protected final boolean _cacheable;
+ protected final int _hits;
+
+ /**
+ * Ctor
+ */
+ protected PageStatsEntry(Map data)
+ {
+ _pageId = (String) data.get("Page_Id");
+ _renderingContext = (String) data.get("Rendering_Context");
+ _workspaceJCR = (String) data.get("Workspace_JCR");
+ _cacheable = (Boolean) data.get("P_Cacheable");
+ _hits = __toInteger(data.get("P_Hits"));
+ }
+
+ private Integer __toInteger(Object value)
+ {
+ Long lValue = (Long) value;
+ return lValue != null ? lValue.intValue() : null;
+ }
+
+ public String getContext()
+ {
+ return _renderingContext + '-' + _workspaceJCR;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (obj == this)
+ {
+ return true;
+ }
+ if (!(obj instanceof PageStatsEntry))
+ {
+ return false;
+ }
+
+ PageStatsEntry that = (PageStatsEntry) obj;
+
+ EqualsBuilder eb = new EqualsBuilder();
+ eb.append(_pageId, that._pageId);
+ eb.append(_renderingContext, that._renderingContext);
+ eb.append(_workspaceJCR, that._workspaceJCR);
+ return eb.isEquals();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ HashCodeBuilder hsb = new HashCodeBuilder();
+ hsb.append(_pageId).append(_renderingContext).append(_workspaceJCR);
+ return hsb.toHashCode();
+ }
+ }
+
+ /**
+ * Object model representing an entry of stats for a PageElement
+ */
+ @SuppressWarnings("javadoc")
+ protected class PageElementStatsEntry
+ {
+ protected final String _pageElementID;
+ protected final String _renderingContext;
+ protected final String _workspaceJCR;
+ protected final Boolean _cacheable;
+ protected final Integer _hits;
+ protected final Integer _cacheHits;
+
+ /**
+ * Ctor
+ */
+ protected PageElementStatsEntry(Map data)
+ {
+ _pageElementID = (String) data.get("Page_Element_Id");
+ _renderingContext = (String) data.get("Rendering_Context");
+ _workspaceJCR = (String) data.get("Workspace_JCR");
+ _cacheable = (Boolean) data.get("PE_Cacheable");
+ _hits = __toInteger(data.get("PE_Hits"));
+ _cacheHits = __toInteger(data.get("PE_Cache_Hits"));
+ }
+
+ private Integer __toInteger(Object value)
+ {
+ Long lValue = (Long) value;
+ return lValue != null ? lValue.intValue() : null;
+ }
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/generators/ServersCacheStatsGenerator.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/generators/ServersCacheStatsGenerator.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/generators/ServersCacheStatsGenerator.java (revision 0)
@@ -0,0 +1,783 @@
+package org.ametys.web.cachemonitoring.generators;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.ProcessingException;
+import org.apache.cocoon.generation.ServiceableGenerator;
+import org.apache.cocoon.xml.AttributesImpl;
+import org.apache.cocoon.xml.XMLUtils;
+import org.apache.commons.lang.StringUtils;
+import org.joda.time.Duration;
+import org.joda.time.format.PeriodFormat;
+import org.xml.sax.SAXException;
+
+import org.ametys.plugins.repository.UnknownAmetysObjectException;
+import org.ametys.runtime.datasource.ConnectionHelper;
+import org.ametys.web.cachemonitoring.core.CacheMonitoringSqlStatements;
+import org.ametys.web.repository.site.Site;
+import org.ametys.web.repository.site.SiteManager;
+import org.ametys.web.site.SitesGenerator;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+
+/**
+ * Cache stats generator grouping data collected across each server cache (http
+ * server/front/back)
+ */
+public class ServersCacheStatsGenerator extends ServiceableGenerator
+{
+ /** List of paths used to during the sanitize process of the server path */
+ protected static final String[] _SPECIAL_PATH_PREFIXS = new String[]{"/skins/", "/plugins/", "/kernel", "/_external/"};
+
+ /**
+ * This multimap associates site names to a list of prefix. This is needed
+ * to sanitize the server path.
+ */
+ protected Multimap _pathSanitizer;
+
+ /**
+ * Multimaps representing the all the paths to the resources in a recursive
+ * way. The map key's are the site names.
+ */
+ protected Map> _pathMaps;
+
+ /**
+ * Multimap containing apache stats entries classified by site name and path
+ */
+ protected Map> _fromApacheStats;
+
+ /**
+ * Multimap containing (only) front stats entries classified by site name
+ * and path
+ */
+ protected Map> _fromFrontOnlyStats;
+
+ /** Map containing back stats entries classified by path */
+ protected Map _backStats;
+
+
+ /** Ametys resolver */
+ protected SiteManager _siteManager;
+
+ @Override
+ public void service(ServiceManager sm) throws ServiceException
+ {
+ super.service(sm);
+ _siteManager = (SiteManager) sm.lookup(SiteManager.ROLE);
+ }
+
+ @Override
+ public void generate() throws IOException, SAXException, ProcessingException
+ {
+ String siteName = parameters.getParameter("siteName", "");
+ Site site = null;
+
+ try
+ {
+ site = _siteManager.getSite(siteName);
+ }
+ catch (UnknownAmetysObjectException e)
+ {
+ String message = "The site '" + siteName + "' does not exist.";
+ getLogger().error(message, e);
+ throw new ProcessingException(message, e);
+ }
+
+ // Retrieve raw info from DB.
+ List rawStats = _getStatsFromDb(siteName);
+
+ _pathMaps = new HashMap>();
+ _fromApacheStats = new HashMap>();
+ _fromFrontOnlyStats = new HashMap>();
+ _backStats = new HashMap();
+ _pathSanitizer = ArrayListMultimap.create();
+
+ // Populate and organize stats maps.
+ _initializeStats(rawStats);
+
+ Long start = null;
+ if (getLogger().isDebugEnabled())
+ {
+ start = System.currentTimeMillis();
+
+ getLogger().debug("Starting to SAX cache statistics for Apache and the Front-office");
+ }
+
+ // Start SAX'ing
+ contentHandler.startDocument();
+ XMLUtils.startElement(contentHandler, "stats");
+
+ // sax info for the given site.
+ _saxStatsBySite(site);
+
+ // sax orphan (resource from front without site info).
+ _saxStatsOrphanEntries();
+
+ XMLUtils.endElement(contentHandler, "stats");
+ contentHandler.endDocument();
+
+ if (getLogger().isDebugEnabled())
+ {
+ if (start != null)
+ {
+ long end = System.currentTimeMillis();
+ String duration = PeriodFormat.getDefault().print(new Duration(end - start).toPeriod());
+ getLogger().debug(String.format("The SAX process of the Apache and Front-office cache statistics took %s", duration));
+ }
+ }
+ }
+
+ @Override
+ public void recycle()
+ {
+ _pathMaps = null;
+ _fromApacheStats = null;
+ _fromFrontOnlyStats = null;
+ _backStats = null;
+ _pathSanitizer = null;
+
+ super.recycle();
+ }
+
+ /**
+ * Retrieves raw statistics information
+ * @param siteName
+ * @return a list of {@link StatsEntry}
+ * @throws ProcessingException
+ */
+ private List _getStatsFromDb(String siteName) throws ProcessingException
+ {
+ Connection conn = null;
+ PreparedStatement ps = null;
+ ResultSet rs = null;
+
+ try
+ {
+ conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID);
+
+ String sql = _getStatsQuery();
+
+ ps = conn.prepareStatement(sql);
+ ps.setString(1, siteName);
+ ps.setString(2, siteName);
+
+ rs = ps.executeQuery();
+
+ return _processCacheStatsResultSet(rs);
+ }
+ catch (SQLException e)
+ {
+ String msg = "SQL Error while retrieving cache stats";
+ getLogger().error(msg);
+ throw new ProcessingException(msg, e);
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ ConnectionHelper.cleanup(ps);
+ ConnectionHelper.cleanup(conn);
+ }
+ }
+
+ private static String _getStatsQuery()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(" SELECT F.Server_Site, F.Server_Path, F.Server_Hits, F.Server_Cache_Hits,");
+ sb.append(" F.Front_Site, F.Front_Path, F.Front_Cacheable, F.Front_Hits, F.Front_Cache_Hits_1, F.Front_Cache_Hits_2,");
+ sb.append(" B.Page_Id, B.Page_Path, B.Cacheable, B.Hits");
+ sb.append(" FROM CACHE_FRONT_STATS F");
+ sb.append(" LEFT OUTER JOIN CACHE_BACK_STATS B");
+ sb.append(" ON F.Front_Path = B.Page_Path AND F.Front_Path is not NULL AND B.Rendering_Context = 'front'");
+ sb.append(" WHERE F.Server_Site = ? OR F.Server_Site = '' OR F.Server_Site is NULL");
+ sb.append(" OR F.Front_Site = ? OR F.Front_Site = '' OR F.Front_Site is NULL");
+
+ return sb.toString();
+ }
+
+ private List _processCacheStatsResultSet(ResultSet rs) throws SQLException
+ {
+ ResultSetMetaData meta = rs.getMetaData();
+ String[] cols = new String[meta.getColumnCount()];
+
+ for (int i = 0; i < cols.length; i++)
+ {
+ cols[i] = meta.getColumnLabel(i + 1);
+ }
+
+ List entries = new ArrayList();
+
+ while (rs.next())
+ {
+ Map data = new HashMap();
+ for (int i = 0; i < cols.length; i++)
+ {
+ data.put(cols[i], rs.getObject(i + 1));
+ }
+
+ entries.add(new RawStatsEntry(data));
+ }
+
+ return entries;
+ }
+
+ /**
+ * Organize the different Map of stats
+ * @param rawStats
+ */
+ private void _initializeStats(List rawStats)
+ {
+ // Iterate over raw entries and do the logic to populate
+ // Apache/FrontOnly/Back stats map.
+ for (RawStatsEntry rawStatsEntry : rawStats)
+ {
+ if (rawStatsEntry.hasApacheInfo())
+ {
+ FrontFromApacheStatsEntry apacheEntry = new FrontFromApacheStatsEntry(rawStatsEntry._data);
+ _addToApacheStats(apacheEntry);
+ }
+ else
+ {
+ FrontFromFrontStatsEntry frontEntry = new FrontFromFrontStatsEntry(rawStatsEntry._data);
+ _addToFrontOnlyStats(frontEntry);
+ }
+
+ if (rawStatsEntry.hasBackInfo())
+ {
+ BackStatsEntry backEntry = new BackStatsEntry(rawStatsEntry._data);
+
+ // remove .html in page name to avoid issues when a page as children pages.
+ String backPath = StringUtils.removeEnd(backEntry._pagePath, ".html");
+ _backStats.put(backPath, backEntry);
+ }
+ }
+ }
+
+ private void _addToApacheStats(FrontFromApacheStatsEntry apacheEntry)
+ {
+ // could be null in case of a resource. That's ok, we will handle this case later when SAX'ing.
+ String siteName = apacheEntry._serverSite;
+
+ Map siteMap = _fromApacheStats.get(siteName);
+ if (siteMap == null)
+ {
+ siteMap = new HashMap();
+ _fromApacheStats.put(siteName, siteMap);
+ }
+
+ // The path must be sanitized to be of the following form:
+ // /site/lang/page-path or /skin/... or /plugins/... etc...
+ String sanitizedPath = apacheEntry.getSanitizedPath();
+
+ // remove .html in page name to avoid issues when a page as children pages.
+ sanitizedPath = StringUtils.removeEnd(sanitizedPath, ".html");
+
+ FrontFromApacheStatsEntry previousApacheEntry = siteMap.put(sanitizedPath, apacheEntry);
+
+ // Must merge the info if a previous entry was already in the map (this can happens when a site as several url aliases)
+ if (previousApacheEntry != null)
+ {
+ apacheEntry.merge(previousApacheEntry);
+ }
+
+ // Also register this new path into the path maps.
+ _registerPath(apacheEntry._serverSite, sanitizedPath);
+ }
+
+ private void _addToFrontOnlyStats(FrontFromFrontStatsEntry frontEntry)
+ {
+ // could be null in case of a resource. That's ok, we will handle this case later when SAX'ing.
+ String siteName = StringUtils.defaultIfEmpty(frontEntry._frontSite, null);
+
+ Map siteMap = _fromFrontOnlyStats.get(siteName);
+ if (siteMap == null)
+ {
+ siteMap = new HashMap();
+ _fromFrontOnlyStats.put(siteName, siteMap);
+ }
+
+ // remove .html in page name to avoid issues when a page as children pages.
+ String frontPath = StringUtils.removeEnd(frontEntry._frontPath, ".html");
+ siteMap.put(frontPath, frontEntry);
+
+ // Also register this new path into the path maps.
+ _registerPath(siteName, frontPath);
+ }
+
+ private void _registerPath(String serverSite, String path)
+ {
+ Multimap pathMap = _pathMaps.get(serverSite);
+ if (pathMap == null)
+ {
+ pathMap = HashMultimap.create(); // does not allow duplicate key-value entries.
+ _pathMaps.put(serverSite, pathMap);
+ }
+
+ String[] splitPath = StringUtils.split(path, '/');
+ _internalRegisterPath(pathMap, StringUtils.EMPTY, new LinkedList(Arrays.asList(splitPath)));
+ }
+
+ /**
+ * Populate the path map using recursion.
+ * @param pathMap Multimap representing the paths to the resources within a site.
+ * @param consumed the consumed part of the current path being registered.
+ * @param tail tail of the current path being registered. The tail will be consumed in the recursive nested calls of this internal function.
+ */
+ private void _internalRegisterPath(Multimap pathMap, String consumed, LinkedList tail)
+ {
+ if (!tail.isEmpty())
+ {
+ String head = tail.removeFirst();
+ pathMap.put(consumed, head);
+ _internalRegisterPath(pathMap, consumed + '/' + head, tail);
+ }
+ }
+
+ private void _saxStatsBySite(Site site) throws SAXException
+ {
+ String siteName = site.getName();
+
+ Multimap pathMap = _pathMaps.get(siteName);
+ if (pathMap == null)
+ {
+ return;
+ }
+
+ Collection entryPoints = pathMap.get(StringUtils.EMPTY);
+ if (entryPoints.isEmpty())
+ {
+ return;
+ }
+
+ Map apacheStats = _fromApacheStats.get(siteName);
+ if (apacheStats == null)
+ {
+ apacheStats = Collections.EMPTY_MAP;
+ }
+
+ Map frontOnlyStats = _fromFrontOnlyStats.get(siteName);
+ if (frontOnlyStats == null)
+ {
+ frontOnlyStats = Collections.EMPTY_MAP;
+ }
+
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("name", siteName);
+ attrs.addCDATAAttribute("id", site.getId());
+ XMLUtils.startElement(contentHandler, "site", attrs);
+
+ for (String name : entryPoints)
+ {
+ _saxStatsEntry(name, '/' + name, pathMap, apacheStats, frontOnlyStats);
+ }
+
+ XMLUtils.endElement(contentHandler, "site");
+ }
+
+ private void _saxStatsEntry(String name, String path, Multimap pathMap, Map apacheEntries, Map frontEntries) throws SAXException
+ {
+ FrontFromApacheStatsEntry apacheEntry = apacheEntries.get(path);
+ FrontFromFrontStatsEntry frontEntry = frontEntries.get(path);
+ BackStatsEntry backEntry = _backStats.get(path);
+
+ AttributesImpl attrs = new AttributesImpl();
+
+ String type = "folder";
+ if (frontEntry != null)
+ {
+ type = frontEntry._frontPath.endsWith(".html") ? "page" : "asset";
+ }
+ else if (apacheEntry != null)
+ {
+ if (apacheEntry.hasFrontInfo())
+ {
+ type = apacheEntry._frontPath.endsWith(".html") ? "page" : "asset";
+ }
+ else
+ {
+ type = apacheEntry._serverPath.endsWith(".html") ? "page" : "asset";
+ }
+ }
+
+ Boolean cacheable = null;
+ if (apacheEntry != null)
+ {
+ // No front info means implicit cache hit on the apache cache.
+ cacheable = apacheEntry.hasFrontInfo() ? apacheEntry._frontCacheable : true;
+ }
+ else if (frontEntry != null)
+ {
+ cacheable = frontEntry._frontCacheable;
+ }
+
+ attrs.addCDATAAttribute("type", type);
+ attrs.addCDATAAttribute("name", "page".equals(type) ? name + ".html" : name);
+ attrs.addCDATAAttribute("path", path);
+ __addAttrIfNotNull(attrs, "cacheable", cacheable);
+ XMLUtils.startElement(contentHandler, "resource", attrs);
+
+ _saxStatsApacheEntry(apacheEntry);
+ _saxStatsFrontEntry(frontEntry);
+ _saxStatsBackEntry(backEntry);
+
+ // Recursive call on children
+ for (String childName : pathMap.get(path))
+ {
+ _saxStatsEntry(childName, path + '/' + childName, pathMap, apacheEntries, frontEntries);
+ }
+
+ XMLUtils.endElement(contentHandler, "resource");
+ }
+
+ private void _saxStatsApacheEntry(FrontFromApacheStatsEntry apacheEntry) throws SAXException
+ {
+ if (apacheEntry == null)
+ {
+ return;
+ }
+
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("type", "apache");
+ __addAttrIfNotNull(attrs, "apacheSite", apacheEntry._serverSite);
+ __addAttrIfNotNull(attrs, "apachePath", apacheEntry._serverPath);
+
+ // Add front info if available
+ if (apacheEntry.hasFrontInfo())
+ {
+ __addAttrIfNotNull(attrs, "frontSite", apacheEntry._frontSite);
+ attrs.addCDATAAttribute("frontPath", apacheEntry._frontPath);
+ attrs.addCDATAAttribute("frontCacheable", String.valueOf(apacheEntry._frontCacheable));
+ }
+
+ XMLUtils.startElement(contentHandler, "entry", attrs);
+
+ XMLUtils.createElement(contentHandler, "apacheHits", String.valueOf(apacheEntry._serverHits));
+ XMLUtils.createElement(contentHandler, "apacheCacheHits", String.valueOf(apacheEntry._serverCacheHits));
+
+ // Add front info if available
+ if (apacheEntry.hasFrontInfo())
+ {
+ XMLUtils.createElement(contentHandler, "frontHits", String.valueOf(apacheEntry._frontHits));
+ XMLUtils.createElement(contentHandler, "frontCacheHits1", String.valueOf(apacheEntry._frontCacheHits1));
+ XMLUtils.createElement(contentHandler, "frontCacheHits2", String.valueOf(apacheEntry._frontCacheHits2));
+ }
+
+ XMLUtils.endElement(contentHandler, "entry");
+ }
+
+ private void _saxStatsFrontEntry(FrontFromFrontStatsEntry frontEntry) throws SAXException
+ {
+ if (frontEntry == null)
+ {
+ return;
+ }
+
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("type", "front");
+ __addAttrIfNotNull(attrs, "site", frontEntry._frontSite);
+ attrs.addCDATAAttribute("path", frontEntry._frontPath);
+ attrs.addCDATAAttribute("cacheable", String.valueOf(frontEntry._frontCacheable));
+ XMLUtils.startElement(contentHandler, "entry", attrs);
+
+ XMLUtils.createElement(contentHandler, "hits", String.valueOf(frontEntry._frontHits));
+ XMLUtils.createElement(contentHandler, "cacheHits1", String.valueOf(frontEntry._frontCacheHits1));
+ XMLUtils.createElement(contentHandler, "cacheHits2", String.valueOf(frontEntry._frontCacheHits2));
+
+ XMLUtils.endElement(contentHandler, "entry");
+ }
+
+ private void _saxStatsBackEntry(BackStatsEntry backEntry) throws SAXException
+ {
+ if (backEntry == null)
+ {
+ return;
+ }
+
+ AttributesImpl attrs = new AttributesImpl();
+ attrs.addCDATAAttribute("type", "back");
+ attrs.addCDATAAttribute("id", backEntry._pageId);
+ attrs.addCDATAAttribute("path", backEntry._pagePath);
+ attrs.addCDATAAttribute("cacheable", String.valueOf(backEntry._cacheable));
+ XMLUtils.startElement(contentHandler, "entry", attrs);
+
+ XMLUtils.createElement(contentHandler, "hits", String.valueOf(backEntry._hits));
+
+ XMLUtils.endElement(contentHandler, "entry");
+ }
+
+ private void _saxStatsOrphanEntries() throws SAXException
+ {
+ Multimap pathMap = _pathMaps.get(null);
+ if (pathMap == null || pathMap.isEmpty())
+ {
+ return;
+ }
+
+ Collection entryPoints = pathMap.get(StringUtils.EMPTY);
+ if (entryPoints.isEmpty())
+ {
+ return;
+ }
+
+ Map apacheStats = _fromApacheStats.get(null);
+ if (apacheStats == null)
+ {
+ apacheStats = Collections.EMPTY_MAP;
+ }
+
+ Map frontOnlyStats = _fromFrontOnlyStats.get(null);
+ if (frontOnlyStats == null)
+ {
+ frontOnlyStats = Collections.EMPTY_MAP;
+ }
+
+ XMLUtils.startElement(contentHandler, "orphans");
+
+ for (String name : entryPoints)
+ {
+ _saxStatsEntry(name, '/' + name, pathMap, apacheStats, frontOnlyStats);
+ }
+
+ XMLUtils.endElement(contentHandler, "orphans");
+ }
+
+ private void __addAttrIfNotNull(AttributesImpl attrs, String localName, String value)
+ {
+ if (StringUtils.isNotEmpty(value))
+ {
+ attrs.addCDATAAttribute(localName, value);
+ }
+ }
+
+ private void __addAttrIfNotNull(AttributesImpl attrs, String localName, Boolean value)
+ {
+ if (value != null)
+ {
+ attrs.addCDATAAttribute(localName, String.valueOf(value));
+ }
+ }
+
+ /**
+ * Object model representing a raw entry of stats retrieved through the DB.
+ */
+ @SuppressWarnings("javadoc")
+ protected class RawStatsEntry
+ {
+ protected final Map _data;
+
+ /**
+ * Ctor
+ */
+ protected RawStatsEntry(Map data)
+ {
+ _data = data;
+ }
+
+ protected boolean hasApacheInfo()
+ {
+ return _data.get("Server_Path") != null;
+ }
+
+ protected boolean hasBackInfo()
+ {
+ return _data.get("Page_Id") != null;
+ }
+ }
+
+ /**
+ * Object model representing an entry of stats for a front resource, coming
+ * from Apache
+ */
+ @SuppressWarnings("javadoc")
+ protected class FrontFromApacheStatsEntry extends FrontFromFrontStatsEntry
+ {
+
+ protected final String _serverSite;
+ protected final String _serverPath;
+ protected Integer _serverHits;
+ protected Integer _serverCacheHits;
+
+ /**
+ * Ctor
+ */
+ protected FrontFromApacheStatsEntry(Map data)
+ {
+ super(data);
+
+ _serverSite = (String) data.get("Server_Site");
+ _serverPath = (String) data.get("Server_Path");
+ _serverHits = __toInteger(data.get("Server_Hits"));
+ _serverCacheHits = __toInteger(data.get("Server_Cache_Hits"));
+ }
+
+ protected void merge(FrontFromApacheStatsEntry that)
+ {
+ if (that == null)
+ {
+ return;
+ }
+
+ _serverHits += that._serverHits;
+ _serverCacheHits += that._serverCacheHits;
+
+ if (that.hasFrontInfo())
+ {
+ _frontSite = that._frontSite;
+ _frontPath = that._frontPath;
+ _frontCacheable = that._frontCacheable;
+
+ _frontHits = hasFrontInfo() ? _frontHits + that._frontHits : that._frontHits;
+ _frontCacheHits1 = hasFrontInfo() ? _frontCacheHits1 + that._frontCacheHits1 : that._frontCacheHits1;
+ _frontCacheHits2 = hasFrontInfo() ? _frontCacheHits2 + that._frontCacheHits2 : that._frontCacheHits2;
+ }
+ }
+
+ protected String getSanitizedPath()
+ {
+ if (hasFrontInfo())
+ {
+ return _frontPath;
+ }
+
+ // initialize allowed prefix path list for this site, given the site url aliases.
+ if (!_pathSanitizer.containsKey(_serverSite))
+ {
+ for (String url : _siteManager.getSite(_serverSite).getUrlAliases())
+ {
+ Matcher matcher = SitesGenerator.URL_PATTERN.matcher(url);
+ if (matcher.matches())
+ {
+ String path = matcher.group(5);
+ if (StringUtils.isNotEmpty(path))
+ {
+ _pathSanitizer.put(_serverSite, path);
+ }
+ }
+ }
+
+ if (!_pathSanitizer.containsKey(_serverSite))
+ {
+ _pathSanitizer.put(_serverSite, null);
+ }
+ }
+
+ // sanitize serverPath
+ Iterator sitePathPrefixIt = _pathSanitizer.get(_serverSite).iterator();
+ boolean sanitized = false;
+ String sanitizedPath = _serverPath;
+
+ while (!sanitized && sitePathPrefixIt.hasNext())
+ {
+ String prefix = sitePathPrefixIt.next();
+
+ // Nothing to do in this case.
+ if (prefix == null)
+ {
+ sanitized = true;
+ }
+ else if (StringUtils.startsWith(_serverPath, prefix))
+ {
+ sanitizedPath = StringUtils.removeStart(_serverPath, prefix);
+ sanitized = true;
+ }
+ }
+
+ // add site name when needed
+ if (!StringUtils.startsWithAny(sanitizedPath, _SPECIAL_PATH_PREFIXS))
+ {
+ sanitizedPath = '/' + _serverSite + sanitizedPath;
+ }
+
+ return sanitizedPath;
+ }
+
+ protected boolean hasFrontInfo()
+ {
+ return _frontPath != null;
+ }
+ }
+
+ /**
+ * Object model representing an entry of stats for a front resource, coming
+ * from the Front (direct request to tomcat, bypassing any Apache HTTP server if any).
+ */
+ @SuppressWarnings("javadoc")
+ protected class FrontFromFrontStatsEntry
+ {
+ protected String _frontSite;
+ protected String _frontPath;
+ protected Boolean _frontCacheable;
+ protected Integer _frontHits;
+ protected Integer _frontCacheHits1;
+ protected Integer _frontCacheHits2;
+
+ /**
+ * Ctor
+ */
+ protected FrontFromFrontStatsEntry(Map data)
+ {
+ _frontSite = (String) data.get("Front_Site");
+ _frontPath = (String) data.get("Front_Path");
+ _frontCacheable = (Boolean) data.get("Front_Cacheable");
+ _frontHits = __toInteger(data.get("Front_Hits"));
+ _frontCacheHits1 = __toInteger(data.get("Front_Cache_Hits_1"));
+ _frontCacheHits2 = __toInteger(data.get("Front_Cache_Hits_2"));
+ }
+
+ protected Integer __toInteger(Object value)
+ {
+ Long lValue = (Long) value;
+ return lValue != null ? lValue.intValue() : null;
+ }
+ }
+
+ /**
+ * Object model representing an entry of stats for a back resource (ie. a
+ * page).
+ */
+ @SuppressWarnings("javadoc")
+ protected class BackStatsEntry
+ {
+ protected final String _pageId;
+ protected final String _pagePath;
+ protected final boolean _cacheable;
+ protected final int _hits;
+
+ /**
+ * Ctor
+ */
+ protected BackStatsEntry(Map data)
+ {
+ _pageId = (String) data.get("Page_Id");
+ _pagePath = (String) data.get("Page_Path");
+ _cacheable = (Boolean) data.get("Cacheable");
+ _hits = __toInteger(data.get("Hits"));
+ }
+
+ private Integer __toInteger(Object value)
+ {
+ Long lValue = (Long) value;
+ return lValue != null ? lValue.intValue() : null;
+ }
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStats.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStats.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStats.java (revision 0)
@@ -0,0 +1,61 @@
+package org.ametys.web.cachemonitoring.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * A ResourceCacheStats is an object that holds cache statistics coming from the
+ * monitoring database or to be inserted/updated in the monitoring database.
+ */
+public interface ResourceCacheStats
+{
+ /** Type of possible ResourceCacheStats */
+ public enum ResourceCacheStatsType
+ {
+ /** APACHE resource with a cache hit*/
+ APACHE_ONLY,
+ /** Front resource with a unique id (coming from apache) */
+ FRONT_FROM_APACHE,
+ /** Front resource without a unique id (direct to tomcat) */
+ FRONT_ONLY,
+ /*** Back page resource */
+ BACK_PAGE,
+ /*** Back page element resource stats */
+ BACK_PAGE_ELEMENT;
+ }
+
+ /**
+ * get type
+ * @return the type of this ResourceCacheStats instance.
+ */
+ public ResourceCacheStatsType getType();
+
+ /**
+ * Must configure the given PreparedStatement for a find query.
+ * @param ps
+ * @throws SQLException
+ */
+ public void configureSqlFind(PreparedStatement ps) throws SQLException;
+
+ /**
+ * Configure the {@link PreparedStatement} used when doing the SQL insert
+ * query.
+ * @param ps The {@link PreparedStatement}
+ * @throws SQLException
+ */
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException;
+
+ /**
+ * Configure the {@link PreparedStatement} used when doing the SQL update
+ * query.
+ * @param ps The {@link PreparedStatement}
+ * @throws SQLException
+ */
+ public void configureSqlUpdate(PreparedStatement ps) throws SQLException;
+
+ /**
+ * Returns the number of hits
+ * @return int the hits
+ */
+ public int getHits();
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringScheduler.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringScheduler.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringScheduler.java (revision 0)
@@ -0,0 +1,109 @@
+package org.ametys.web.cachemonitoring.core;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.apache.avalon.framework.activity.Disposable;
+import org.apache.avalon.framework.activity.Initializable;
+import org.apache.avalon.framework.logger.LogEnabled;
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.joda.time.Duration;
+import org.joda.time.format.PeriodFormat;
+
+@SuppressWarnings("javadoc")
+public class CacheMonitoringScheduler extends TimerTask implements Initializable, Serviceable, Disposable, LogEnabled
+{
+ /** Logger */
+ protected Logger _logger;
+
+ /** Timer. */
+ protected Timer _timer;
+
+ /** Resource Access Monitor */
+ protected ResourceAccessMonitor _resourceAccessMonitor;
+
+ /** Cache stats updater */
+ protected CacheMonitoringUpdater _cacheStatsUpdater;
+
+ @Override
+ public void enableLogging(Logger logger)
+ {
+ _logger = logger;
+ }
+
+ @Override
+ public void initialize() throws Exception
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Scheduling the CacheMonitoringScheduler component to run its task each hours");
+ }
+
+ // Daemon thread
+ _timer = new Timer("CacheMonitoringScheduler", true);
+
+ // The task is each hour, starting at h15m if not too late.
+ // It is a quarter after the execution of the task of the FrontCacheMonitoringScheduler
+ GregorianCalendar calendar = new GregorianCalendar();
+ int minute = calendar.get(Calendar.MINUTE);
+
+ if (minute > 10)
+ {
+ calendar.add(Calendar.HOUR_OF_DAY, 1);
+ }
+
+ calendar.set(Calendar.MINUTE, 15);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ _timer.scheduleAtFixedRate(this, calendar.getTime(), 60 * 60 * 1000);
+ }
+
+ @Override
+ public void service(ServiceManager manager) throws ServiceException
+ {
+ _cacheStatsUpdater = (CacheMonitoringUpdater) manager.lookup(CacheMonitoringUpdater.ROLE);
+ _resourceAccessMonitor = (ResourceAccessMonitor) manager.lookup(ResourceAccessMonitor.ROLE);
+ }
+
+ @Override
+ public void dispose()
+ {
+ cancel();
+ _timer.cancel();
+ _logger = null;
+ }
+
+ @Override
+ public void run()
+ {
+ _logger.info("Starting the cache monitoring process from the back-office.");
+
+ // Export pending records
+ long start = System.currentTimeMillis();
+ _resourceAccessMonitor.exportPendings();
+ long endExport = System.currentTimeMillis();
+
+ // Then update stats.
+ _cacheStatsUpdater.updateStats();
+ long endUpdate = System.currentTimeMillis();
+
+ // And purge processed raw data
+ _cacheStatsUpdater.purgeRawData();
+ long end = System.currentTimeMillis();
+
+ String totalDuration = PeriodFormat.getDefault().print(new Duration(end - start).toPeriod());
+ String exportDuration = PeriodFormat.getDefault().print(new Duration(endExport - start).toPeriod());
+ String updateDuration = PeriodFormat.getDefault().print(new Duration(endUpdate - endExport).toPeriod());
+ String purgeDuration = PeriodFormat.getDefault().print(new Duration(end - endUpdate).toPeriod());
+
+ _logger.info(String.format(
+ "The whole back-office cache monitoring process took %s [export raw data to db : %s, update cache statistics: %s, purge processed data: %s]",
+ totalDuration, exportDuration, updateDuration, purgeDuration));
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStatsFactory.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStatsFactory.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceCacheStatsFactory.java (revision 0)
@@ -0,0 +1,47 @@
+package org.ametys.web.cachemonitoring.core;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import org.ametys.web.cachemonitoring.back.PageElementCacheStats;
+import org.ametys.web.cachemonitoring.back.PageResourceCacheStats;
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats.ResourceCacheStatsType;
+import org.ametys.web.cachemonitoring.front.FrontFromApacheCacheStats;
+import org.ametys.web.cachemonitoring.front.FrontOnlyCacheStats;
+import org.ametys.web.cachemonitoring.server.ApacheOnlyCacheStats;
+
+/**
+ * Factory for ResourceCacheStats objects
+ */
+public final class ResourceCacheStatsFactory
+{
+ private ResourceCacheStatsFactory()
+ {
+ // ignore
+ }
+
+ /**
+ * Create a ResourceCacheStats of the desired type.
+ * @param type
+ * @param data
+ * @return The instance of this new ResourceCacheStats object.
+ */
+ public static ResourceCacheStats create(ResourceCacheStatsType type, Map data)
+ {
+ switch (type)
+ {
+ case APACHE_ONLY:
+ return new ApacheOnlyCacheStats(data);
+ case FRONT_ONLY:
+ return new FrontOnlyCacheStats(data);
+ case FRONT_FROM_APACHE:
+ return new FrontFromApacheCacheStats(data);
+ case BACK_PAGE:
+ return new PageResourceCacheStats(data);
+ case BACK_PAGE_ELEMENT:
+ return new PageElementCacheStats(data);
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringUpdater.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringUpdater.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringUpdater.java (revision 0)
@@ -0,0 +1,423 @@
+package org.ametys.web.cachemonitoring.core;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.joda.time.Duration;
+import org.joda.time.format.PeriodFormat;
+
+import org.ametys.runtime.datasource.ConnectionHelper;
+import org.ametys.web.cachemonitoring.core.ResourceAccess.MonitoredResourceType;
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats.ResourceCacheStatsType;
+
+/**
+ * The cache monitoring updater. It updates the statistics in the monitoring database, given the new request entries.
+ */
+public class CacheMonitoringUpdater extends AbstractLogEnabled implements Component
+{
+ /** Avalon ROLE. */
+ public static final String ROLE = CacheMonitoringUpdater.class.getName();
+
+ /**
+ * Update the cache stats tables.
+ */
+ void updateStats()
+ {
+ _logDebug("Updating cache stats tables...");
+
+ long start = System.currentTimeMillis();
+ int totalUpdated = 0;
+
+ for (ResourceCacheStatsType rcst : ResourceCacheStatsType.values())
+ {
+ int updated = 0;
+ updated = update(rcst);
+ totalUpdated += updated;
+
+ _logDebug(String.format("%s new request log processed for resource type '%s'", updated, rcst));
+ }
+
+ String durationStr = PeriodFormat.getDefault().print(new Duration(System.currentTimeMillis() - start).toPeriod());
+ _logDebug(String.format("\tTotal count of new request log that have been processed : %s", totalUpdated));
+ _logDebug(String.format("Update of the cache stats tables took %s", durationStr));
+ }
+
+ private int update(ResourceCacheStatsType type)
+ {
+ Connection conn = null;
+ try
+ {
+ conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID);
+ conn.setAutoCommit(false);
+
+ int updated = update(conn, type);
+ conn.commit();
+
+ return updated;
+ }
+ catch (Exception e)
+ {
+ _logError("Exception during update cache stats for type '" + type + "'. Performing a rollback of the transaction.", e);
+
+ try
+ {
+ if (conn != null)
+ {
+ conn.rollback();
+ }
+ }
+ catch (SQLException sqle)
+ {
+ _logError("Error : SQLException. Problem while performing the rollback.", sqle);
+ }
+
+ return 0;
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(conn);
+ }
+ }
+
+ private int update(Connection conn, ResourceCacheStatsType type) throws SQLException
+ {
+ ResultSet rs = null;
+
+ try
+ {
+ long id = getMaxIdToProcess(conn, type);
+ if (id <= 0)
+ {
+ // nothing to do.
+ return 0;
+ }
+
+ // Retrieves resources to process + update cache stats
+ UpdateStatsInfo info = new UpdateStatsInfo(conn, type);
+ int hits = updateCacheStats(conn, info);
+
+ // Mark treated resource access as processed
+ int processed = markResourcesAsProcessed(conn, type, id);
+
+ _logDebug(String.format(
+ "It appears to be %s new hits for the ResourceCacheStatsType '%s'. Also, %s access logs have been marked has processed.",
+ hits, type, processed));
+
+ // Special case for front from apache resources
+ if (type == ResourceCacheStatsType.FRONT_FROM_APACHE)
+ {
+ int apacheProcessed = markApacheResourcesAsProcessed(conn);
+ _logDebug(String.format(
+ "Comparing processed and apacheProcessed for type 'FRONT_FROM_APACHE': %s-%s . These values should be equals.",
+ processed, apacheProcessed));
+ }
+
+ return processed;
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ }
+ }
+
+ private long getMaxIdToProcess(Connection conn, ResourceCacheStatsType type) throws SQLException
+ {
+ final String getMaxIdSql = CacheMonitoringSqlStatements.getMaxIdToProcessSql(type);
+
+ Statement st = null;
+ ResultSet rs = null;
+ try
+ {
+ st = conn.createStatement();
+ rs = st.executeQuery(getMaxIdSql);
+
+ if (rs.next())
+ {
+ return rs.getInt(1);
+ }
+ return -1;
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ ConnectionHelper.cleanup(st);
+ }
+ }
+
+ private int updateCacheStats(Connection conn, UpdateStatsInfo info) throws SQLException
+ {
+ int hits = 0;
+
+ for (ResourceCacheStats rcs : info.getNewStats())
+ {
+ updateCacheStats(conn, rcs);
+ hits += rcs.getHits();
+ }
+
+ return hits;
+ }
+
+ private void updateCacheStats(Connection conn, ResourceCacheStats rcs) throws SQLException
+ {
+ if (findCacheStatsEntry(conn, rcs))
+ {
+ updateCacheStatsEntry(conn, rcs);
+ }
+ else
+ {
+ insertCacheStatsEntry(conn, rcs);
+ }
+ }
+
+ private boolean findCacheStatsEntry(Connection conn, ResourceCacheStats rcs) throws SQLException
+ {
+ final String sql = CacheMonitoringSqlStatements.findCacheStatsEntrySql(rcs.getType());
+
+ PreparedStatement ps = null;
+ ResultSet rs = null;
+ try
+ {
+ ps = conn.prepareStatement(sql);
+ rcs.configureSqlFind(ps);
+
+ rs = ps.executeQuery();
+
+ // result must be > 0
+ rs.next();
+ return rs.getInt(1) > 0;
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ ConnectionHelper.cleanup(ps);
+ }
+ }
+
+ private void updateCacheStatsEntry(Connection conn, ResourceCacheStats rcs) throws SQLException
+ {
+ final String sql = CacheMonitoringSqlStatements.updateCacheStatsEntrySql(rcs.getType());
+
+ PreparedStatement ps = null;
+ ResultSet rs = null;
+ try
+ {
+ ps = conn.prepareStatement(sql);
+ rcs.configureSqlUpdate(ps);
+
+ ps.executeUpdate();
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ ConnectionHelper.cleanup(ps);
+ }
+ }
+
+ private void insertCacheStatsEntry(Connection conn, ResourceCacheStats rcs) throws SQLException
+ {
+ final String sql = CacheMonitoringSqlStatements.insertCacheStatsSql(rcs.getType());
+
+ PreparedStatement ps = null;
+ ResultSet rs = null;
+ try
+ {
+ ps = conn.prepareStatement(sql);
+ rcs.configureSqlInsert(ps);
+
+ ps.executeUpdate();
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ ConnectionHelper.cleanup(ps);
+ }
+ }
+
+ private int markResourcesAsProcessed(Connection conn, ResourceCacheStatsType type, long id) throws SQLException
+ {
+ final String markProcessedSql = CacheMonitoringSqlStatements.getMarkProcessedSql(type);
+
+ PreparedStatement ps = null;
+ try
+ {
+ ps = conn.prepareStatement(markProcessedSql);
+ ps.setLong(1, id);
+ return ps.executeUpdate();
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(ps);
+ }
+ }
+
+ private int markApacheResourcesAsProcessed(Connection conn) throws SQLException
+ {
+ final String markApacheProcessedSql = CacheMonitoringSqlStatements.getMarkApacheProcessedSql();
+
+ Statement st = null;
+ try
+ {
+ st = conn.createStatement();
+ return st.executeUpdate(markApacheProcessedSql);
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(st);
+ }
+ }
+
+ private class UpdateStatsInfo
+ {
+ private final ResourceCacheStatsType _type;
+ private List _newCacheStats;
+
+ public UpdateStatsInfo(Connection conn, ResourceCacheStatsType type) throws SQLException
+ {
+ _type = type;
+ _newCacheStats = new ArrayList();
+
+ updateProcess(conn);
+ }
+
+ private void updateProcess(Connection conn) throws SQLException
+ {
+ Statement st = null;
+ ResultSet rs = null;
+ try
+ {
+ st = conn.createStatement();
+ rs = getResourcesAccessToProcess(st);
+ analyzeResultSet(rs);
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(rs);
+ ConnectionHelper.cleanup(st);
+ }
+ }
+
+ private ResultSet getResourcesAccessToProcess(Statement st) throws SQLException
+ {
+ final String getToProcessSql = CacheMonitoringSqlStatements.getResourceAccessToProcess(_type);
+ return st.executeQuery(getToProcessSql);
+ }
+
+ private void analyzeResultSet(ResultSet rs) throws SQLException
+ {
+ _logDebug("Start analyzing raw update info for type : " + _type);
+
+ ResultSetMetaData meta = rs.getMetaData();
+ String[] cols = new String[meta.getColumnCount()];
+
+ for (int i = 0; i < cols.length; i++)
+ {
+ cols[i] = meta.getColumnLabel(i + 1);
+ }
+
+ while (rs.next())
+ {
+ Map data = new HashMap();
+ for (int i = 0; i < cols.length; i++)
+ {
+ data.put(cols[i], rs.getObject(i + 1));
+ }
+
+ ResourceCacheStats rcs = ResourceCacheStatsFactory.create(_type, data);
+ _newCacheStats.add(rcs);
+ }
+
+ _logDebug("Analysis of the raw info ended well.");
+ }
+
+ public List getNewStats()
+ {
+ return _newCacheStats;
+ }
+ }
+
+ /**
+ * Purge the processed raw request entries.
+ */
+ void purgeRawData()
+ {
+ _logDebug("Purging raw monitoring data...");
+
+ long start = System.currentTimeMillis();
+ int totalPurged = 0;
+
+ for (MonitoredResourceType mrt : MonitoredResourceType.values())
+ {
+ totalPurged += purge(mrt);
+ }
+
+ String durationStr = PeriodFormat.getDefault().print(new Duration(System.currentTimeMillis() - start).toPeriod());
+ _logDebug(String.format("%s row purged from the database in %s", totalPurged, durationStr));
+ }
+
+ private int purge(MonitoredResourceType type)
+ {
+ Connection conn = null;
+ try
+ {
+ conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID);
+ return purge(conn, type);
+ }
+ catch (Exception e)
+ {
+ _logError("Exception during the purge of the raw data for type '" + type + "'.", e);
+ return 0;
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(conn);
+ }
+ }
+
+ private int purge(Connection conn, MonitoredResourceType type) throws SQLException
+ {
+ final String purgeSql = CacheMonitoringSqlStatements.getPurgeSql(type);
+
+ Statement st = null;
+ try
+ {
+ st = conn.createStatement();
+ return st.executeUpdate(purgeSql);
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(st);
+ }
+ }
+
+ /**
+ * Helper log debug method
+ * @param msg The msg to log at the debug level.
+ */
+ protected void _logDebug(String msg)
+ {
+ if (getLogger().isDebugEnabled())
+ {
+ getLogger().debug(msg);
+ }
+ }
+
+ /**
+ * Helper log error method
+ * @param msg The msg to log at the error level.
+ * @param t the throwable
+ */
+ protected void _logError(String msg, Throwable t)
+ {
+ getLogger().error(msg, t);
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccessMonitor.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccessMonitor.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccessMonitor.java (revision 0)
@@ -0,0 +1,250 @@
+package org.ametys.web.cachemonitoring.core;
+
+import java.sql.BatchUpdateException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.avalon.framework.activity.Initializable;
+import org.apache.avalon.framework.component.Component;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.joda.time.Duration;
+import org.joda.time.format.PeriodFormat;
+
+import org.ametys.runtime.datasource.ConnectionHelper;
+import org.ametys.web.cachemonitoring.core.ResourceAccess.MonitoredResourceType;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimaps;
+
+/**
+ * The RessourceAccessMonitor collects the resources that have been requested,
+ * and export them into a database.
+ */
+public class ResourceAccessMonitor extends AbstractLogEnabled implements Initializable, Component
+{
+ /** Avalon ROLE. */
+ public static final String ROLE = ResourceAccessMonitor.class.getName();
+
+ /**
+ * List of pending {@link ResourceAccess} waiting to be exported to the
+ * database.
+ */
+ protected ListMultimap _pendingRecords;
+
+ private final ExecutorService _importerPool = Executors.newCachedThreadPool();
+
+ @Override
+ public void initialize() throws Exception
+ {
+ _pendingRecords = Multimaps.synchronizedListMultimap(LinkedListMultimap.create());
+ }
+
+ void exportPendings()
+ {
+ _logDebug("Start to insert pending records.");
+
+ long start = System.currentTimeMillis();
+ int totalCount = 0;
+
+ Map> toExport = new HashMap>();
+ synchronized (_pendingRecords)
+ {
+ totalCount = _pendingRecords.size();
+ for (MonitoredResourceType mrt : _pendingRecords.keySet())
+ {
+ toExport.put(mrt, _pendingRecords.removeAll(mrt));
+ }
+ }
+
+ int successCount = 0;
+ if (!toExport.isEmpty())
+ {
+ Connection conn = null;
+ try
+ {
+ conn = ConnectionHelper.getConnection(CacheMonitoringSqlStatements.MONITORING_DATASOURCE_ID);
+ conn.setAutoCommit(false);
+ successCount = export(conn, toExport);
+ conn.commit();
+ }
+ catch (Exception e)
+ {
+ _logError("Exception during export to DB. Performing a rollback of the transaction.", e);
+
+ try
+ {
+ if (conn != null)
+ {
+ conn.rollback();
+ }
+ }
+ catch (SQLException sqle)
+ {
+ _logError("Error : SQLException. Problem while performing the rollback.", sqle);
+ }
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(conn);
+ }
+ }
+
+ String durationStr = PeriodFormat.getDefault().print(new Duration(System.currentTimeMillis() - start).toPeriod());
+ _logDebug(String.format("%s/%s pending records exported into db in %s", successCount, totalCount, durationStr));
+ }
+
+ private int export(Connection conn, Map> toExport)
+ {
+ try
+ {
+ int success = 0;
+ for (MonitoredResourceType mrt : toExport.keySet())
+ {
+ success += exportByType(conn, mrt, toExport.get(mrt).iterator());
+ }
+ return success;
+ }
+ catch (SQLException e)
+ {
+ e.printStackTrace();
+ }
+
+ return 0;
+ }
+
+ private int exportByType(Connection conn, MonitoredResourceType mrt, Iterator rait) throws SQLException
+ {
+ PreparedStatement ps = null;
+ ResourceAccess ra = null;
+ try
+ {
+ String insertSql = CacheMonitoringSqlStatements.insertResourceAccessSql(mrt);
+ ps = conn.prepareStatement(insertSql, Statement.NO_GENERATED_KEYS);
+
+ while (rait.hasNext())
+ {
+ ra = rait.next();
+ ra.configureSqlInsert(ps);
+ ps.addBatch();
+ }
+
+ try
+ {
+ return ps.executeBatch().length;
+ }
+ catch (BatchUpdateException e)
+ {
+ // more detailed treatment is possible through e.getUpdateCounts()
+ _logError("Batch exception while inserting new records into of type '" + mrt + "' to the DB", e);
+ }
+ }
+ finally
+ {
+ ConnectionHelper.cleanup(ps);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Add a new {@link ResourceAccess} to the monitored resources.
+ *
+ * @param ra The resource access object.
+ * @return a {@link Future} to use if you would like to immediately block
+ * waiting for the add operation to finish.
+ */
+ public Future addAccessRecord(ResourceAccess ra)
+ {
+ return _importerPool.submit(new RecordAdder(ra));
+ }
+
+ /**
+ * Add a new {@link ResourceAccess} to the monitored resources.
+ *
+ * @param lra The list of resource access objects.
+ * @return a {@link Future} to use if you would like to immediately block
+ * waiting for the add operation to finish.
+ */
+ public Future addAccessRecords(List lra)
+ {
+ return _importerPool.submit(new RecordsAdder(lra));
+ }
+
+ /**
+ * {@link Callable} that add a {@link ResourceAccess} in the list of pending
+ * records.
+ */
+ private class RecordAdder implements Callable
+ {
+ private final ResourceAccess _ra;
+
+ public RecordAdder(ResourceAccess ra)
+ {
+ _ra = ra;
+ }
+
+ @Override
+ public Void call() throws Exception
+ {
+ _pendingRecords.put(_ra.getType(), _ra);
+ return null;
+ }
+ }
+
+ /**
+ * {@link Callable} that add each element a list of {@link ResourceAccess} into the
+ * list of pending records.
+ */
+ private class RecordsAdder implements Callable
+ {
+ private final List _lra;
+
+ public RecordsAdder(List lra)
+ {
+ _lra = lra;
+ }
+
+ @Override
+ public Void call() throws Exception
+ {
+ for (ResourceAccess ra : _lra)
+ {
+ _pendingRecords.put(ra.getType(), ra);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Helper log debug method
+ * @param msg The msg to log at the debug level.
+ */
+ protected void _logDebug(String msg)
+ {
+ if (getLogger().isDebugEnabled())
+ {
+ getLogger().debug(msg);
+ }
+ }
+
+ /**
+ * Helper log error method
+ * @param msg The msg to log at the error level.
+ * @param t the throwable
+ */
+ protected void _logError(String msg, Throwable t)
+ {
+ getLogger().error(msg, t);
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringSqlStatements.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringSqlStatements.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/CacheMonitoringSqlStatements.java (revision 0)
@@ -0,0 +1,459 @@
+package org.ametys.web.cachemonitoring.core;
+
+import java.util.Arrays;
+
+import org.apache.commons.lang.StringUtils;
+
+import org.ametys.web.cachemonitoring.core.ResourceAccess.MonitoredResourceType;
+import org.ametys.web.cachemonitoring.core.ResourceCacheStats.ResourceCacheStatsType;
+
+/**
+ * Gather SQL statements related to the cache monitoring process.
+ */
+public final class CacheMonitoringSqlStatements
+{
+ /** Datasource id of the cache monitoring datasource */
+ public static final String MONITORING_DATASOURCE_ID = "cache.monitoring.datasource";
+
+ // >--------------------- ACCESS TABLES ------------------------<
+
+ // FIXME Config
+ // private static final String __APACHE_RESOURCE_ACCESS_TABLE_NAME = Config.SERVER_REQUESTS_TABLE_NAME;
+ // private static final String __FRONT_RESOURCE_ACCESS_TABLE_NAME = Config.FRONT_REQUESTS_TABLE_NAME;
+ // private static final String __PAGE_RESOURCE_ACCESS_TABLE_NAME = Config.PAGE_REQUESTS_TABLE_NAME;
+ // private static final String __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME = Config.PAGE_ELEMENT_REQUESTS_TABLE_NAME;
+
+ private static final String __APACHE_RESOURCE_ACCESS_TABLE_NAME = "Server_Requests";
+ private static final String __FRONT_RESOURCE_ACCESS_TABLE_NAME = "Front_Requests";
+ private static final String __PAGE_RESOURCE_ACCESS_TABLE_NAME = "Back_Requests";
+ private static final String __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME = "Back_Page_Element_Requests";
+
+
+ private static final String __INSERT_RESOURCE_ACCESS_SQL = "INSERT IGNORE INTO $tbl_name$ ($insert_cols$) values ($insert_values$)";
+ private static final String __TBL_NAME_PATTERN = "$tbl_name$";
+ private static final String __INSERT_COLS_CLAUSE_PATTERN = "$insert_cols$";
+ private static final String __INSERT_VALUES_CLAUSE_PATTERN = "$insert_values$";
+ private static final String __APACHE_INSERT_RESOURCE_COLS_CLAUSE = "Unique_Id, Site, Remote_Host_Name, Request_Date, Method, Path, Query_String, Protocol, Ori_Status_Code, Ret_Status_Code, Cache_Hit, Referer, User_Agent";
+ private static final String __FRONT_INSERT_RESOURCE_COLS_CLAUSE = "Unique_Id, Internal_Uuid, Site, Ametys_Path, Cacheable, Cache_Hit_1, Cache_Hit_2";
+ private static final String __PAGE_INSERT_RESOURCE_COLS_CLAUSE = "Internal_Uuid, Page_Id, Page_Path, Rendering_Context, Workspace_JCR, Cacheable";
+ private static final String __PAGE_ELEMENT_INSERT_RESOURCE_COLS_CLAUSE = "Internal_Uuid, Page_Element_Id, Page_Element_Type, Page_Id, Rendering_Context, Workspace_JCR, Cacheable, Cache_Hit";
+
+ private static final String __GROUP_FIELDS_PATTERN = "$group_fields$";
+ private static final String __COLS_FIELDS_PATTERN = "$cols_fields$";
+ private static final String __INTERNAL_GET_RES_PATTERN = "$internal_get_res$";
+ private static final String __GET_RESOURCE_TO_PROCESS = "SELECT count(*) AS increment, $cols_fields$ FROM $tbl_name$ T $internal_get_res$ GROUP BY $group_fields$ ORDER BY max(T.Id)";
+
+ private static final String __APACHE_PROCESS_GROUP_FIELDS = "Site, Path, Cache_Hit";
+ private static final String __FRONT_PROCESS_GROUP_FIELDS = "Site, Ametys_Path, Cacheable, Cache_Hit_1, Cache_Hit_2";
+ private static final String __FRONT_APACHE_PROCESS_GROUP_FIELDS = "S.Site, S.Path, S.Cache_Hit, T.Site, T.Ametys_Path, T.Cacheable, T.Cache_Hit_1, T.Cache_Hit_2";
+ private static final String __PAGE_PROCESS_GROUP_FIELDS = "Page_Id, Page_Path, Rendering_Context, Workspace_JCR, Cacheable";
+ private static final String __PAGE_ELEMENT_PROCESS_GROUP_FIELDS = "Page_Element_Id, Page_Id, Rendering_Context, Workspace_JCR, Cacheable, Cache_Hit";
+
+ private static final String __APACHE_PROCESS_COLS_FIELDS = __APACHE_PROCESS_GROUP_FIELDS;
+ private static final String __FRONT_PROCESS_COLS_FIELDS = __FRONT_PROCESS_GROUP_FIELDS;
+ private static final String __FRONT_APACHE_PROCESS_COLS_FIELDS = "S.Site AS S_Site, S.Path AS S_Path, S.Cache_Hit AS S_Cache_Hit, T.Site AS F_Site, T.Ametys_Path AS F_Ametys_Path, T.Cacheable AS F_Cacheable, T.Cache_Hit_1 AS F_Cache_Hit_1, T.Cache_Hit_2 AS F_Cache_Hit_2";
+ private static final String __PAGE_PROCESS_COLS_FIELDS = __PAGE_PROCESS_GROUP_FIELDS;
+ private static final String __PAGE_ELEMENT_PROCESS_COLS_FIELDS = __PAGE_ELEMENT_PROCESS_GROUP_FIELDS;
+
+ private static final String __APACHE_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false AND Cache_Hit = true AND (Ori_Status_Code = 200 OR Ori_Status_Code = 304)";
+ private static final String __FRONT_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false AND Unique_Id is NULL";
+ private static final String __FRONT_APACHE_INTERNAL_GET_RES_PATTERN = "INNER JOIN " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " S ON T.Unique_Id = S.Unique_Id AND T.Processed = false AND T.Unique_Id is not NULL AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304)";
+ private static final String __PAGE_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false";
+ private static final String __PAGE_ELEMENT_INTERNAL_GET_RES_PATTERN = "WHERE PROCESSED = false";
+
+ private static final String __GET_MAX_ID_TO_PROCESS = "SELECT max(Id) FROM $tbl_name$ WHERE PROCESSED = false";
+ private static final String __GET_MAX_ID_TO_PROCESS_APACHE = "SELECT max(Id) FROM $tbl_name$ WHERE PROCESSED = false AND (Ori_Status_Code = 200 OR Ori_Status_Code = 304)";
+ private static final String __GET_MAX_ID_TO_PROCESS_FRONT = "SELECT max(Id) FROM $tbl_name$ WHERE PROCESSED = false AND Unique_Id is NULL";
+ private static final String __GET_MAX_ID_TO_PROCESS_FRONT_APACHE = "SELECT max(F.Id) FROM $tbl_name$ AS F INNER JOIN " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " S ON F.Unique_Id = S.Unique_Id AND F.Processed = false AND F.Unique_Id is not NULL AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304)";
+
+ private static final String __MARK_AS_PROCESSED = "UPDATE $tbl_name$ SET PROCESSED = true WHERE Id <= ? AND Processed = false";
+ private static final String __MARK_AS_PROCESSED_APACHE = "UPDATE $tbl_name$ AS S LEFT OUTER JOIN " + __FRONT_RESOURCE_ACCESS_TABLE_NAME + " F ON S.Unique_Id = F.Unique_Id SET S.Processed = true WHERE S.Id <= ? AND F.Unique_Id is NULL AND S.Processed = false AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304)";
+ private static final String __MARK_AS_PROCESSED_FRONT = "UPDATE $tbl_name$ SET PROCESSED = true WHERE Id <= ? AND Processed = false AND Unique_Id is NULL";
+ private static final String __MARK_AS_PROCESSED_FRONT_APACHE = "UPDATE $tbl_name$ AS F INNER JOIN " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " S ON F.Unique_Id = S.Unique_Id AND F.Processed = false AND F.Unique_Id is not NULL AND (S.Ori_Status_Code = 200 OR S.Ori_Status_Code = 304) SET F.Processed = true WHERE F.Id <= ?";
+ private static final String __MARK_AS_PROCESSED_APACHE_AFTER_FRONT_APACHE = "UPDATE " + __APACHE_RESOURCE_ACCESS_TABLE_NAME + " AS S INNER JOIN " + __FRONT_RESOURCE_ACCESS_TABLE_NAME + " F ON S.Unique_Id = F.Unique_Id AND S.Processed = false AND F.Processed = true SET S.Processed = true";
+
+ private static final String __PURGE_PROCESSED_SQL = "DELETE FROM $tbl_name$ WHERE PROCESSED = true";
+
+ // >--------------------- CACHE STATS TABLES ------------------------<
+
+ // FIXME Config
+ // private static final String __APACHE_CACHE_STATS_TABLE_NAME = Config.SERVER_CACHE_STATS_TABLE_NAME;
+ // private static final String __FRONT_CACHE_STATS_TABLE_NAME = Config.FRONT_CACHE_STATS_TABLE_NAME;
+ // private static final String __PAGE_CACHE_STATS_TABLE_NAME = Config.PAGE_CACHE_STATS_TABLE_NAME;
+ // private static final String __PAGE_ELEMENT_CACHE_STATS_TABLE_NAME = Config.PAGE_ELEMENT_CACHE_STATS_TABLE_NAME;
+
+ private static final String __FRONT_CACHE_STATS_TABLE_NAME = "Cache_Front_Stats";
+ private static final String __PAGE_CACHE_STATS_TABLE_NAME = "Cache_Back_Stats";
+ private static final String __PAGE_ELEMENT_CACHE_STATS_TABLE_NAME = "Cache_Back_Page_Element_Stats";
+
+
+ private static final String __INSERT_CACHE_STATS = "INSERT INTO $tbl_name$ ($insert_cols$) values ($insert_values$)";
+ private static final String __APACHE_INSERT_CACHE_COLS_CLAUSE = "Server_Site, Server_Path, Server_Hits, Server_Cache_Hits";
+ private static final String __FRONT_INSERT_CACHE_COLS_CLAUSE = "Front_Site, Front_Path, Front_Cacheable, Front_Hits, Front_Cache_Hits_1, Front_Cache_Hits_2";
+ private static final String __FRONT_APACHE_INSERT_CACHE_COLS_CLAUSE = __APACHE_INSERT_CACHE_COLS_CLAUSE + ", " + __FRONT_INSERT_CACHE_COLS_CLAUSE;
+ private static final String __PAGE_INSERT_CACHE_COLS_CLAUSE = "Page_Id, Page_Path, Rendering_Context, Workspace_JCR, Cacheable, Hits";
+ private static final String __PAGE_ELEMENT_INSERT_CACHE_COLS_CLAUSE = "Page_Element_Id, Page_Id, Rendering_Context, Workspace_JCR, Cacheable, Hits, Cache_Hits";
+
+ private static final String __FIND_CACHE_STATS = "SELECT count(1) FROM $tbl_name$ WHERE $where_clause$";
+ private static final String __WHERE_CLAUSE_PATTERN = "$where_clause$";
+ private static final String __APACHE_WHERE_CLAUSE = "Server_Site = ? AND Server_Path = ?";
+ private static final String __FRONT_WHERE_CLAUSE = "Front_Site = ? AND Front_Path = ? AND Server_Path is NULL";
+ private static final String __FRONT_APACHE_WHERE_CLAUSE = __APACHE_WHERE_CLAUSE;
+ private static final String __PAGE_WHERE_CLAUSE = "Page_Id = ? AND Rendering_Context = ? AND Workspace_JCR = ?";
+ private static final String __PAGE_ELEMENT_WHERE_CLAUSE = "Page_Element_Id = ? AND Page_Id = ? AND Rendering_Context = ? AND Workspace_JCR = ?";
+
+ private static final String __UPDATE_CACHE_STATS = "UPDATE $tbl_name$ SET $set_clause$ WHERE $where_clause$";
+ private static final String __SET_CLAUSE_PATTERN = "$set_clause$";
+ private static final String __APACHE_SET_CLAUSE = "Server_Hits = Server_Hits + ?, Server_Cache_Hits = Server_Cache_Hits + ?";
+ private static final String __FRONT_SET_CLAUSE = "Front_Cacheable = ?, Front_Hits = Front_Hits + ?, Front_Cache_Hits_1 = Front_Cache_Hits_1 + ?, Front_Cache_Hits_2 = Front_Cache_Hits_2 + ?";
+ private static final String __FRONT_APACHE_SET_CLAUSE = __APACHE_SET_CLAUSE + ", Front_Site = ?, Front_Path = ?, " + __FRONT_SET_CLAUSE;
+ private static final String __PAGE_SET_CLAUSE = "Page_Path = ?, Cacheable = ?, Hits = Hits + ?";
+ private static final String __PAGE_ELEMENT_SET_CLAUSE = "Cacheable = ?, Hits = Hits + ?, Cache_Hits = Cache_Hits + ?";
+
+ private CacheMonitoringSqlStatements()
+ {
+ // ignore
+ }
+
+ /**
+ * Insert resource access
+ * @param type
+ * @return The sql query
+ */
+ public static String insertResourceAccessSql(MonitoredResourceType type)
+ {
+ String insertCols = null;
+ switch (type)
+ {
+ case APACHE_RESOURCE:
+ insertCols = __APACHE_INSERT_RESOURCE_COLS_CLAUSE;
+ break;
+ case FRONT_RESOURCE:
+ insertCols = __FRONT_INSERT_RESOURCE_COLS_CLAUSE;
+ break;
+ case BACK_PAGE_RESOURCE:
+ insertCols = __PAGE_INSERT_RESOURCE_COLS_CLAUSE;
+ break;
+ case BACK_PAGE_ELEMENT:
+ insertCols = __PAGE_ELEMENT_INSERT_RESOURCE_COLS_CLAUSE;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(MonitoredResourceType.values()));
+ }
+
+ String[] insertValuesArray = StringUtils.split(insertCols, ", ");
+ Arrays.fill(insertValuesArray, "?");
+ String insertValues = StringUtils.join(insertValuesArray, ", ");
+
+ String sql = StringUtils.replaceOnce(__INSERT_RESOURCE_ACCESS_SQL, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ sql = StringUtils.replaceOnce(sql, __INSERT_COLS_CLAUSE_PATTERN, insertCols);
+ sql = StringUtils.replaceOnce(sql, __INSERT_VALUES_CLAUSE_PATTERN, insertValues);
+
+ return sql;
+ }
+
+ /**
+ * The purge sql query
+ * @param type
+ * @return The sql query
+ */
+ public static String getPurgeSql(MonitoredResourceType type)
+ {
+ return StringUtils.replaceOnce(__PURGE_PROCESSED_SQL, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ }
+
+ private static String getResourceAccessTableName(MonitoredResourceType type)
+ {
+ String tblName = null;
+ switch (type)
+ {
+ case APACHE_RESOURCE:
+ tblName = __APACHE_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ case FRONT_RESOURCE:
+ tblName = __FRONT_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ case BACK_PAGE_RESOURCE:
+ tblName = __PAGE_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ case BACK_PAGE_ELEMENT:
+ tblName = __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(MonitoredResourceType.values()));
+ }
+
+ return tblName;
+ }
+
+ /**
+ * Get resource access to be processed
+ * @param type
+ * @return The sql query
+ */
+ public static String getResourceAccessToProcess(ResourceCacheStatsType type)
+ {
+ String colsFields = null;
+ String groupFields = null;
+ String internalClause = null;
+ switch (type)
+ {
+ case APACHE_ONLY:
+ colsFields = __APACHE_PROCESS_COLS_FIELDS;
+ groupFields = __APACHE_PROCESS_GROUP_FIELDS;
+ internalClause = __APACHE_INTERNAL_GET_RES_PATTERN;
+ break;
+ case FRONT_ONLY:
+ colsFields = __FRONT_PROCESS_COLS_FIELDS;
+ groupFields = __FRONT_PROCESS_GROUP_FIELDS;
+ internalClause = __FRONT_INTERNAL_GET_RES_PATTERN;
+ break;
+ case FRONT_FROM_APACHE:
+ colsFields = __FRONT_APACHE_PROCESS_COLS_FIELDS;
+ groupFields = __FRONT_APACHE_PROCESS_GROUP_FIELDS;
+ internalClause = __FRONT_APACHE_INTERNAL_GET_RES_PATTERN;
+ break;
+ case BACK_PAGE:
+ colsFields = __PAGE_PROCESS_COLS_FIELDS;
+ groupFields = __PAGE_PROCESS_GROUP_FIELDS;
+ internalClause = __PAGE_INTERNAL_GET_RES_PATTERN;
+ break;
+ case BACK_PAGE_ELEMENT:
+ colsFields = __PAGE_ELEMENT_PROCESS_COLS_FIELDS;
+ groupFields = __PAGE_ELEMENT_PROCESS_GROUP_FIELDS;
+ internalClause = __PAGE_ELEMENT_INTERNAL_GET_RES_PATTERN;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+
+ String sql = StringUtils.replaceOnce(__GET_RESOURCE_TO_PROCESS, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ sql = StringUtils.replaceOnce(sql, __COLS_FIELDS_PATTERN, colsFields);
+ sql = StringUtils.replaceOnce(sql, __GROUP_FIELDS_PATTERN, groupFields);
+ sql = StringUtils.replaceOnce(sql, __INTERNAL_GET_RES_PATTERN, internalClause);
+
+ return sql;
+ }
+
+ /**
+ * Get the max id of the of the resources to process
+ * @param type
+ * @return The sql query
+ */
+ public static String getMaxIdToProcessSql(ResourceCacheStatsType type)
+ {
+ switch (type)
+ {
+ case BACK_PAGE:
+ case BACK_PAGE_ELEMENT:
+ return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ case APACHE_ONLY:
+ return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ case FRONT_ONLY:
+ return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS_FRONT, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ case FRONT_FROM_APACHE:
+ return StringUtils.replaceOnce(__GET_MAX_ID_TO_PROCESS_FRONT_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+ }
+
+ /**
+ * The mark as processed sql query
+ * @param type
+ * @return The sql query
+ */
+ public static String getMarkProcessedSql(ResourceCacheStatsType type)
+ {
+ switch (type)
+ {
+ case BACK_PAGE:
+ case BACK_PAGE_ELEMENT:
+ return StringUtils.replaceOnce(__MARK_AS_PROCESSED, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ case APACHE_ONLY:
+ return StringUtils.replaceOnce(__MARK_AS_PROCESSED_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ case FRONT_ONLY:
+ return StringUtils.replaceOnce(__MARK_AS_PROCESSED_FRONT, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ case FRONT_FROM_APACHE:
+ return StringUtils.replaceOnce(__MARK_AS_PROCESSED_FRONT_APACHE, __TBL_NAME_PATTERN, getResourceAccessTableName(type));
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+ }
+
+ /**
+ * The mark as processed sql query.
+ * This is a special case for the type FRONT_FROM_APACHE.
+ * After having marked as processed the front resources, the corresponding apache resource must be marked too.
+ * @return The sql query
+ */
+ public static String getMarkApacheProcessedSql()
+ {
+ return __MARK_AS_PROCESSED_APACHE_AFTER_FRONT_APACHE;
+ }
+
+ private static String getResourceAccessTableName(ResourceCacheStatsType type)
+ {
+ String tblName = null;
+ switch (type)
+ {
+ case APACHE_ONLY:
+ tblName = __APACHE_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ case FRONT_FROM_APACHE:
+ case FRONT_ONLY:
+ tblName = __FRONT_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ case BACK_PAGE:
+ tblName = __PAGE_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ case BACK_PAGE_ELEMENT:
+ tblName = __PAGE_ELEMENT_RESOURCE_ACCESS_TABLE_NAME;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+
+ return tblName;
+ }
+
+ /**
+ * The find sql cache stats entry query
+ * @param type
+ * @return The sql query
+ */
+ public static String findCacheStatsEntrySql(ResourceCacheStatsType type)
+ {
+ String whereClause = null;
+ switch (type)
+ {
+ case APACHE_ONLY:
+ whereClause = __APACHE_WHERE_CLAUSE;
+ break;
+ case FRONT_ONLY:
+ whereClause = __FRONT_WHERE_CLAUSE;
+ break;
+ case FRONT_FROM_APACHE:
+ whereClause = __FRONT_APACHE_WHERE_CLAUSE;
+ break;
+ case BACK_PAGE:
+ whereClause = __PAGE_WHERE_CLAUSE;
+ break;
+ case BACK_PAGE_ELEMENT:
+ whereClause = __PAGE_ELEMENT_WHERE_CLAUSE;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+
+ String sql = StringUtils.replaceOnce(__FIND_CACHE_STATS, __TBL_NAME_PATTERN, getCacheStatsTableName(type));
+ sql = StringUtils.replaceOnce(sql, __WHERE_CLAUSE_PATTERN, whereClause);
+
+ return sql;
+ }
+
+ /**
+ * The insert sql cache stats entry query
+ * @param type
+ * @return The sql query
+ */
+ public static String insertCacheStatsSql(ResourceCacheStatsType type)
+ {
+ String insertCols = null;
+ switch (type)
+ {
+ case APACHE_ONLY:
+ insertCols = __APACHE_INSERT_CACHE_COLS_CLAUSE;
+ break;
+ case FRONT_ONLY:
+ insertCols = __FRONT_INSERT_CACHE_COLS_CLAUSE;
+ break;
+ case FRONT_FROM_APACHE:
+ insertCols = __FRONT_APACHE_INSERT_CACHE_COLS_CLAUSE;
+ break;
+ case BACK_PAGE:
+ insertCols = __PAGE_INSERT_CACHE_COLS_CLAUSE;
+ break;
+ case BACK_PAGE_ELEMENT:
+ insertCols = __PAGE_ELEMENT_INSERT_CACHE_COLS_CLAUSE;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+
+ String[] insertValuesArray = StringUtils.split(insertCols, ", ");
+ Arrays.fill(insertValuesArray, "?");
+ String insertValues = StringUtils.join(insertValuesArray, ", ");
+
+ String sql = StringUtils.replaceOnce(__INSERT_CACHE_STATS, __TBL_NAME_PATTERN, getCacheStatsTableName(type));
+ sql = StringUtils.replaceOnce(sql, __INSERT_COLS_CLAUSE_PATTERN, insertCols);
+ sql = StringUtils.replaceOnce(sql, __INSERT_VALUES_CLAUSE_PATTERN, insertValues);
+
+ return sql;
+ }
+
+ /**
+ * The update sql cache stats entry query
+ * @param type
+ * @return The sql query
+ */
+ public static String updateCacheStatsEntrySql(ResourceCacheStatsType type)
+ {
+ String setClause = null;
+ String whereClause = null;
+ switch (type)
+ {
+ case APACHE_ONLY:
+ setClause = __APACHE_SET_CLAUSE;
+ whereClause = __APACHE_WHERE_CLAUSE;
+ break;
+ case FRONT_ONLY:
+ setClause = __FRONT_SET_CLAUSE;
+ whereClause = __FRONT_WHERE_CLAUSE;
+ break;
+ case FRONT_FROM_APACHE:
+ setClause = __FRONT_APACHE_SET_CLAUSE;
+ whereClause = __FRONT_APACHE_WHERE_CLAUSE;
+ break;
+ case BACK_PAGE:
+ setClause = __PAGE_SET_CLAUSE;
+ whereClause = __PAGE_WHERE_CLAUSE;
+ break;
+ case BACK_PAGE_ELEMENT:
+ setClause = __PAGE_ELEMENT_SET_CLAUSE;
+ whereClause = __PAGE_ELEMENT_WHERE_CLAUSE;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+
+ String sql = StringUtils.replaceOnce(__UPDATE_CACHE_STATS, __TBL_NAME_PATTERN, getCacheStatsTableName(type));
+ sql = StringUtils.replaceOnce(sql, __SET_CLAUSE_PATTERN, setClause);
+ sql = StringUtils.replaceOnce(sql, __WHERE_CLAUSE_PATTERN, whereClause);
+
+ return sql;
+ }
+
+ private static String getCacheStatsTableName(ResourceCacheStatsType type)
+ {
+ String tblName = null;
+ switch (type)
+ {
+ case APACHE_ONLY:
+ case FRONT_ONLY:
+ case FRONT_FROM_APACHE:
+ tblName = __FRONT_CACHE_STATS_TABLE_NAME;
+ break;
+ case BACK_PAGE:
+ tblName = __PAGE_CACHE_STATS_TABLE_NAME;
+ break;
+ case BACK_PAGE_ELEMENT:
+ tblName = __PAGE_ELEMENT_CACHE_STATS_TABLE_NAME;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal type. Allowed types are : " + Arrays.asList(ResourceCacheStatsType.values()));
+ }
+
+ return tblName;
+ }
+}
Index: main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccess.java
===================================================================
--- main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccess.java (revision 0)
+++ main/plugin-web/src/org/ametys/web/cachemonitoring/core/ResourceAccess.java (revision 0)
@@ -0,0 +1,42 @@
+package org.ametys.web.cachemonitoring.core;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Monitored resources. Each access to theses resources is monitored in order to
+ * be able to analyze cache efficiency and to calculate meaningful statistics
+ */
+public interface ResourceAccess
+{
+ /** Type of monitored resources */
+ public enum MonitoredResourceType
+ {
+ /** APACHE resource */
+ APACHE_RESOURCE,
+ /** Front resource */
+ FRONT_RESOURCE,
+ /*** Back page resource */
+ BACK_PAGE_RESOURCE,
+ /*** Back page element resource */
+ BACK_PAGE_ELEMENT;
+ }
+
+ /**
+ * get type
+ *
+ * @return the type of this {@link ResourceAccess} instance.
+ */
+ public MonitoredResourceType getType();
+
+ /**
+ * Configure the {@link PreparedStatement} used when doing the SQL insert
+ * query. This method must be consistent with the {@link #configureSqlInsert(PreparedStatement)}
+ * method.
+ *
+ * @param ps The {@link PreparedStatement}
+ * @throws SQLException
+ */
+ public void configureSqlInsert(PreparedStatement ps) throws SQLException;
+}
+
Index: main/plugin-web/src/org/ametys/web/repository/site/SiteGenerator.java
===================================================================
--- main/plugin-web/src/org/ametys/web/repository/site/SiteGenerator.java (revision 20474)
+++ main/plugin-web/src/org/ametys/web/repository/site/SiteGenerator.java (working copy)
@@ -74,6 +74,7 @@
attr.addAttribute("", "id", "id", "CDATA", site.getId());
attr.addAttribute("", "name", "name", "CDATA", site.getName());
attr.addAttribute("", "title", "title", "CDATA", StringUtils.defaultString(site.getTitle()));
+ attr.addAttribute("", "path", "path", "CDATA", site.getSitePath());
XMLUtils.startElement(contentHandler, "site", attr);
_saxSite (site);
XMLUtils.endElement(contentHandler, "site");
@@ -84,7 +85,7 @@
private void _saxSite (Site site) throws SAXException
{
CompositeMetadata metaHolder = site.getMetadataHolder();
-
+
XMLUtils.startElement(contentHandler, "metadata");
String[] metadataNames = metaHolder.getMetadataNames();
for (int i = 0; i < metadataNames.length; i++)
@@ -100,5 +101,5 @@
XMLUtils.endElement(contentHandler, "metadata");
}
-
+
}
Index: main/plugin-web/sitemap-back.xmap
===================================================================
--- main/plugin-web/sitemap-back.xmap (revision 20474)
+++ main/plugin-web/sitemap-back.xmap (working copy)
@@ -28,6 +28,8 @@
+
+
@@ -300,6 +302,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: main/workspace-web/src/org/ametys/web/repository/IsPageCacheableAction.java
===================================================================
--- main/workspace-web/src/org/ametys/web/repository/IsPageCacheableAction.java (revision 20474)
+++ main/workspace-web/src/org/ametys/web/repository/IsPageCacheableAction.java (working copy)
@@ -32,14 +32,15 @@
import org.apache.cocoon.environment.SourceResolver;
import org.ametys.plugins.repository.AmetysObjectIterable;
+import org.ametys.web.cachemonitoring.back.PageResourceAccess;
import org.ametys.web.inputdata.InputData;
import org.ametys.web.inputdata.InputDataExtensionPoint;
import org.ametys.web.pageaccess.PageAccessInfo;
import org.ametys.web.pageaccess.PageAccessManager;
import org.ametys.web.repository.page.Page;
+import org.ametys.web.repository.page.Page.PageType;
import org.ametys.web.repository.page.Zone;
import org.ametys.web.repository.page.ZoneItem;
-import org.ametys.web.repository.page.Page.PageType;
import org.ametys.web.repository.page.ZoneItem.ZoneType;
import org.ametys.web.service.Service;
import org.ametys.web.service.ServiceExtensionPoint;
@@ -66,15 +67,21 @@
public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
{
Request request = ObjectModelHelper.getRequest(objectModel);
-
Page page = (Page) request.getAttribute(Page.class.getName());
- if (_isCacheable(page))
+ boolean isCacheable = _isCacheable(page);
+ if (isCacheable)
{
Response response = ObjectModelHelper.getResponse(objectModel);
response.addHeader("X-Ametys-Cacheable", "true");
}
-
+
+ // Page access monitoring.
+ String uuid = request.getHeader("X-Ametys-FO-UUID");
+ PageResourceAccess pageAccess = new PageResourceAccess(uuid, page.getId(), request.getRequestURI());
+ pageAccess.setCacheable(isCacheable);
+ request.setAttribute("PAGE_ACCESS", pageAccess);
+
return EMPTY_MAP;
}
Index: main/workspace-web/src/org/ametys/web/repository/PageGenerator.java
===================================================================
--- main/workspace-web/src/org/ametys/web/repository/PageGenerator.java (revision 20474)
+++ main/workspace-web/src/org/ametys/web/repository/PageGenerator.java (working copy)
@@ -52,6 +52,10 @@
import org.ametys.runtime.authentication.AuthorizationRequiredException;
import org.ametys.runtime.util.IgnoreRootHandler;
import org.ametys.web.cache.pageelement.PageElementCache;
+import org.ametys.web.cachemonitoring.back.PageElementAccess;
+import org.ametys.web.cachemonitoring.back.PageElementAccess.PageElementType;
+import org.ametys.web.cachemonitoring.back.PageResourceAccess;
+import org.ametys.web.cachemonitoring.core.ResourceAccessMonitor;
import org.ametys.web.renderingcontext.RenderingContext;
import org.ametys.web.renderingcontext.RenderingContextHandler;
import org.ametys.web.repository.content.SharedContent;
@@ -105,11 +109,17 @@
/** The service assignment handler. */
private ServicesAssignmentHandler _serviceAssignmentHandler;
+ /** The resource access monitoring component */
+ private ResourceAccessMonitor _resourceAccessMonitor;
+
+ /** The monitored resource access */
+ private PageResourceAccess _pageAccess;
+
private int _zoneItemsInCache;
private int _zoneItemsSaxed;
private int _zonesSaxed;
-
+
@Override
public void service(ServiceManager serviceManager) throws ServiceException
{
@@ -124,6 +134,7 @@
_renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE);
_cTypeAssignmentHandler = (ContentTypesAssignmentHandler) serviceManager.lookup(ContentTypesAssignmentHandler.ROLE);
_serviceAssignmentHandler = (ServicesAssignmentHandler) serviceManager.lookup(ServicesAssignmentHandler.ROLE);
+ _resourceAccessMonitor = (ResourceAccessMonitor) serviceManager.lookup(ResourceAccessMonitor.ROLE);
}
@Override
@@ -131,12 +142,12 @@
{
long t0 = System.currentTimeMillis();
+ Request request = ObjectModelHelper.getRequest(objectModel);
+
_zoneItemsInCache = 0;
_zoneItemsSaxed = 0;
_zonesSaxed = 0;
-
- Request request = ObjectModelHelper.getRequest(objectModel);
-
+
Page page = (Page) request.getAttribute(Page.class.getName());
String title = page.getTitle();
@@ -149,13 +160,19 @@
throw new IllegalStateException("Cannot invoke the PageGenerator on a Page without a PageContent");
}
+ // Monitor the access to this page.
+ _pageAccess = (PageResourceAccess) request.getAttribute("PAGE_ACCESS");
+ _pageAccess.setRenderingContext(renderingContext);
+ _pageAccess.setWorkspaceJCR(workspace);
+ _resourceAccessMonitor.addAccessRecord(_pageAccess);
+
contentHandler.startDocument();
AttributesImpl attrs = new AttributesImpl();
attrs.addCDATAAttribute("title", title);
attrs.addCDATAAttribute("long-title", page.getLongTitle());
attrs.addCDATAAttribute("id", page.getId());
XMLUtils.startElement(contentHandler, "page", attrs);
-
+
try
{
XMLUtils.startElement(contentHandler, "metadata");
@@ -187,16 +204,17 @@
}
catch (AmetysRepositoryException e)
{
+ _pageAccess = null;
throw new ProcessingException("Unable to SAX page metadata", e);
}
-
- AttributesImpl pcattrs = new AttributesImpl();
+
+ AttributesImpl pcattrs = new AttributesImpl();
pcattrs.addCDATAAttribute("modifiable", Boolean.toString(page instanceof ModifiablePage));
pcattrs.addCDATAAttribute("moveable", Boolean.toString(page instanceof MoveablePage));
XMLUtils.startElement(contentHandler, "pageContents", pcattrs);
long t1 = System.currentTimeMillis();
-
+
try
{
// Iterate on existing zones
@@ -204,7 +222,7 @@
{
String zoneName = zone.getName();
AmetysObjectIterable extends ZoneItem> zoneItems = zone.getZoneItems();
-
+
_saxZone(page, zoneName, zoneItems, workspace, siteName, renderingContext);
}
@@ -223,6 +241,7 @@
}
catch (AmetysRepositoryException ex)
{
+ _pageAccess = null;
throw new ProcessingException("Unable to get Content", ex);
}
@@ -239,8 +258,10 @@
XMLUtils.endElement(contentHandler, "pageContents");
XMLUtils.endElement(contentHandler, "page");
contentHandler.endDocument();
+
+ _pageAccess = null;
}
-
+
/**
* Sax a zone
* @param page The page
@@ -259,7 +280,7 @@
AttributesImpl zoneAttrs = new AttributesImpl();
zoneAttrs.addCDATAAttribute("name", zoneName);
-
+
if (localZoneItems == null || !localZoneItems.hasNext())
{
// zone is empty => try to inherit
@@ -271,7 +292,7 @@
localZoneItems = parentPageZone.getZoneItems();
}
}
-
+
Request request = ObjectModelHelper.getRequest(objectModel);
request.setAttribute(Zone.class.getName(), zoneName);
@@ -281,10 +302,10 @@
_saxAvailableServices(page, zoneName);
_saxZoneItems(page, localZoneItems, workspace, site, renderingContext);
-
+
XMLUtils.endElement(contentHandler, "zone");
request.setAttribute(Zone.class.getName(), null);
-
+
}
/**
@@ -331,7 +352,7 @@
/**
* Sax zone items
- * @param page
+ * @param page
* @param zoneItems The zone items to sax
* @throws SAXException
* @throws MalformedURLException
@@ -355,18 +376,23 @@
String id = zoneItem.getId();
ZoneType type = zoneItem.getType();
-
+
request.setAttribute(ZoneItem.class.getName(), zoneItem);
AttributesImpl zoneItemAttrs = new AttributesImpl();
zoneItemAttrs.addCDATAAttribute("id", id);
+ PageElementAccess zoneAccess = _pageAccess.createPageElementAccess(id, PageElementType.fromZoneItemType(type));
+
// try to get content from cache
SaxBuffer cachedContent = _zoneItemCache.getPageElement(workspace, site, _getType(zoneItem), id, page.getId(), renderingContext);
-
+
if (cachedContent != null)
{
_zoneItemsInCache++;
+ zoneAccess.setCacheable(true);
+ zoneAccess.setCacheHit(true);
+
cachedContent.toSAX(contentHandler);
}
else
@@ -381,14 +407,17 @@
{
String serviceId = zoneItem.getServiceId();
Service service = _serviceExtPt.getExtension(serviceId);
-
+
if (service != null)
- {
+ {
isCacheable = service.isCacheable(zoneItem);
}
// if service is null, an exception will be thrown later
}
+ zoneAccess.setCacheable(isCacheable);
+ zoneAccess.setCacheHit(false);
+
SaxBuffer buffer = null;
ContentHandler handler; // the actual ContentHandler, either the real one, or a buffer
if (isCacheable)
@@ -402,10 +431,10 @@
}
XMLUtils.startElement(handler, "zoneItem", zoneItemAttrs);
-
+
XMLUtils.startElement(handler, "information");
XMLUtils.createElement(handler, "type", type.toString());
-
+
Source src = null;
Exception ex = null;
if (type == ZoneType.CONTENT)
@@ -414,7 +443,7 @@
{
Content content = zoneItem.getContent();
String metadataSetName = StringUtils.defaultString(zoneItem.getMetadataSetName(), "main");
-
+
XMLUtils.createElement(handler, "contentId", content.getId());
XMLUtils.createElement(handler, "contentName", content.getName());
XMLUtils.createElement(handler, "metadataSetName", metadataSetName);
@@ -462,7 +491,7 @@
{
String serviceId = zoneItem.getServiceId();
Service service = _serviceExtPt.getExtension(serviceId);
-
+
if (service == null)
{
ex = new ProcessingException("Unable to get service for name '" + serviceId + "'");
@@ -472,20 +501,20 @@
AttributesImpl serviceAttrs = new AttributesImpl();
serviceAttrs.addCDATAAttribute("id", service.getId());
XMLUtils.startElement(handler, "type-information", serviceAttrs);
-
+
service.getLabel().toSAX(handler, "label");
service.getDescription().toSAX(handler, "description");
XMLUtils.createElement(handler, "smallIcon", service.getSmallIcon());
XMLUtils.createElement(handler, "mediumIcon", service.getMediumIcon());
-
+
XMLUtils.endElement(handler, "type-information");
-
+
src = resolver.resolveURI(service.getURL(), null, _getParameters(service, zoneItem));
}
}
-
+
XMLUtils.endElement(handler, "information");
-
+
if (src == null)
{
getLogger().error("Unable to display zone item", ex);
@@ -515,7 +544,7 @@
resolver.release(src);
}
}
-
+
XMLUtils.endElement(handler, "zoneItem");
// finally store the buffered data in the cache and SAX it to the pipeline
@@ -526,6 +555,9 @@
}
}
+ // Monitor the access to this zone item.
+ _resourceAccessMonitor.addAccessRecord(zoneAccess);
+
// Empty content request attributes
request.setAttribute(Content.class.getName(), null);
// Empty zone item request attribute
@@ -619,7 +651,7 @@
getLogger().error("The page '" + childPage.getId() + "' cannot inherit a zone '" + zoneName + "' of template '" + templateName + "' in skin '" + skinId + "' as asked for page '" + page.getId() + "' in site '" + site.getName() + "'", e);
return null;
}
-
+
// This zone is not defined for the template
if (zoneDef == null)
{
@@ -632,7 +664,7 @@
{
return null;
}
-
+
// Get the parent page (that is a container page)
Page parentPage = page;
do
@@ -656,8 +688,8 @@
// No inheritance for this template
return null;
}
-
- // Finally we will inherit from the parentPage and the zone inheritanceSrc
+
+ // Finally we will inherit from the parentPage and the zone inheritanceSrc
return _inherit(childPage, parentPage, inheritanceSrc);
}
@@ -689,7 +721,7 @@
{
CompositeMetadata serviceParams = zoneItem.getServiceParameters();
Map params = new HashMap();
-
+
for (ServiceParameterOrRepeater serviceParameter : service.getParameters().values())
{
if (serviceParameter instanceof ServiceParameter)
@@ -716,7 +748,7 @@
return params;
}
-
+
/**
* Get the values of a repeater in a service instance.
* @param repeater the repeater definition.
Index: main/workspace-web/src/org/ametys/web/inputdata/InputDataGenerator.java
===================================================================
--- main/workspace-web/src/org/ametys/web/inputdata/InputDataGenerator.java (revision 20474)
+++ main/workspace-web/src/org/ametys/web/inputdata/InputDataGenerator.java (working copy)
@@ -28,6 +28,10 @@
import org.ametys.plugins.repository.provider.WorkspaceSelector;
import org.ametys.web.cache.pageelement.PageElementCache;
+import org.ametys.web.cachemonitoring.back.PageElementAccess;
+import org.ametys.web.cachemonitoring.back.PageElementAccess.PageElementType;
+import org.ametys.web.cachemonitoring.back.PageResourceAccess;
+import org.ametys.web.cachemonitoring.core.ResourceAccessMonitor;
import org.ametys.web.renderingcontext.RenderingContext;
import org.ametys.web.renderingcontext.RenderingContextHandler;
import org.ametys.web.repository.page.Page;
@@ -45,6 +49,9 @@
private RenderingContextHandler _renderingconContextHandler;
private SiteManager _sitesManager;
+ /** The resource access monitoring component */
+ private ResourceAccessMonitor _resourceAccessMonitor;
+
@Override
public void service(ServiceManager sManager) throws ServiceException
{
@@ -54,8 +61,9 @@
_renderingconContextHandler = (RenderingContextHandler) sManager.lookup(RenderingContextHandler.ROLE);
_workspaceSelector = (WorkspaceSelector) sManager.lookup(WorkspaceSelector.ROLE);
_sitesManager = (SiteManager) sManager.lookup(SiteManager.ROLE);
+ _resourceAccessMonitor = (ResourceAccessMonitor) sManager.lookup(ResourceAccessMonitor.ROLE);
}
-
+
@Override
public void generate() throws SAXException, ProcessingException
{
@@ -66,7 +74,7 @@
String siteName;
Site site;
String pageId = null;
-
+
Page page = (Page) request.getAttribute(Page.class.getName());
if (page != null)
{
@@ -83,6 +91,9 @@
String workspace = _workspaceSelector.getWorkspace();
RenderingContext renderingContext = _renderingconContextHandler.getRenderingContext();
+ // Access monitoring
+ PageResourceAccess pageAccess = (PageResourceAccess) request.getAttribute("PAGE_ACCESS");
+
contentHandler.startDocument();
XMLUtils.startElement(contentHandler, "inputData");
@@ -90,17 +101,25 @@
{
InputData inputData = _inputDataExtensionPoint.getExtension(id);
+ PageElementAccess inputDataAccess = pageAccess.createPageElementAccess(id, PageElementType.INPUTDATA);
+
// lookup cached content
SaxBuffer cachedContent = _inputDataCache.getPageElement(workspace, siteName, id, pageId, renderingContext);
if (cachedContent != null)
{
+ inputDataAccess.setCacheable(true);
+ inputDataAccess.setCacheHit(true);
+
cachedContent.toSAX(contentHandler);
}
else
{
boolean isCacheable = inputData.isCacheable(site, page);
+ inputDataAccess.setCacheable(isCacheable);
+ inputDataAccess.setCacheHit(false);
+
SaxBuffer buffer = null;
ContentHandler handler; // the actual ContentHandler, either the real one, or a buffer
if (isCacheable)
@@ -122,6 +141,9 @@
buffer.toSAX(contentHandler);
}
}
+
+ // Monitor the access to this zone item.
+ _resourceAccessMonitor.addAccessRecord(inputDataAccess);
}
XMLUtils.endElement(contentHandler, "inputData");