You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sirona.apache.org by rm...@apache.org on 2013/10/30 10:50:53 UTC

svn commit: r1537021 [10/10] - in /incubator/sirona/trunk: ./ reporting/src/main/java/org/apache/sirona/reporting/web/ reporting/src/main/java/org/apache/sirona/reporting/web/handler/ reporting/src/main/java/org/apache/sirona/reporting/web/plugin/repor...

Added: incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/jquery.tablesorter.js
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/jquery.tablesorter.js?rev=1537021&view=auto
==============================================================================
--- incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/jquery.tablesorter.js (added)
+++ incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/jquery.tablesorter.js Wed Oct 30 09:50:51 2013
@@ -0,0 +1,1031 @@
+/*
+ * 
+ * TableSorter 2.0 - Client-side table sorting with ease!
+ * Version 2.0.5b
+ * @requires jQuery v1.2.3
+ * 
+ * Copyright (c) 2007 Christian Bach
+ * Examples and docs at: http://tablesorter.com
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ * 
+ */
+/**
+ * 
+ * @description Create a sortable table with multi-column sorting capabilitys
+ * 
+ * @example $('table').tablesorter();
+ * @desc Create a simple tablesorter interface.
+ * 
+ * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] });
+ * @desc Create a tablesorter interface and sort on the first and secound column column headers.
+ * 
+ * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });
+ *          
+ * @desc Create a tablesorter interface and disableing the first and second  column headers.
+ *      
+ * 
+ * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } });
+ * 
+ * @desc Create a tablesorter interface and set a column parser for the first
+ *       and second column.
+ * 
+ * 
+ * @param Object
+ *            settings An object literal containing key/value pairs to provide
+ *            optional settings.
+ * 
+ * 
+ * @option String cssHeader (optional) A string of the class name to be appended
+ *         to sortable tr elements in the thead of the table. Default value:
+ *         "header"
+ * 
+ * @option String cssAsc (optional) A string of the class name to be appended to
+ *         sortable tr elements in the thead on a ascending sort. Default value:
+ *         "headerSortUp"
+ * 
+ * @option String cssDesc (optional) A string of the class name to be appended
+ *         to sortable tr elements in the thead on a descending sort. Default
+ *         value: "headerSortDown"
+ * 
+ * @option String sortInitialOrder (optional) A string of the inital sorting
+ *         order can be asc or desc. Default value: "asc"
+ * 
+ * @option String sortMultisortKey (optional) A string of the multi-column sort
+ *         key. Default value: "shiftKey"
+ * 
+ * @option String textExtraction (optional) A string of the text-extraction
+ *         method to use. For complex html structures inside td cell set this
+ *         option to "complex", on large tables the complex option can be slow.
+ *         Default value: "simple"
+ * 
+ * @option Object headers (optional) An array containing the forces sorting
+ *         rules. This option let's you specify a default sorting rule. Default
+ *         value: null
+ * 
+ * @option Array sortList (optional) An array containing the forces sorting
+ *         rules. This option let's you specify a default sorting rule. Default
+ *         value: null
+ * 
+ * @option Array sortForce (optional) An array containing forced sorting rules.
+ *         This option let's you specify a default sorting rule, which is
+ *         prepended to user-selected rules. Default value: null
+ * 
+ * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever
+ *         to use String.localeCampare method or not. Default set to true.
+ * 
+ * 
+ * @option Array sortAppend (optional) An array containing forced sorting rules.
+ *         This option let's you specify a default sorting rule, which is
+ *         appended to user-selected rules. Default value: null
+ * 
+ * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter
+ *         should apply fixed widths to the table columns. This is usefull when
+ *         using the pager companion plugin. This options requires the dimension
+ *         jquery plugin. Default value: false
+ * 
+ * @option Boolean cancelSelection (optional) Boolean flag indicating if
+ *         tablesorter should cancel selection of the table headers text.
+ *         Default value: true
+ * 
+ * @option Boolean debug (optional) Boolean flag indicating if tablesorter
+ *         should display debuging information usefull for development.
+ * 
+ * @type jQuery
+ * 
+ * @name tablesorter
+ * 
+ * @cat Plugins/Tablesorter
+ * 
+ * @author Christian Bach/christian.bach@polyester.se
+ */
+
+(function ($) {
+    $.extend({
+        tablesorter: new
+        function () {
+
+            var parsers = [],
+                widgets = [];
+
+            this.defaults = {
+                cssHeader: "header",
+                cssAsc: "headerSortUp",
+                cssDesc: "headerSortDown",
+                cssChildRow: "expand-child",
+                sortInitialOrder: "asc",
+                sortMultiSortKey: "shiftKey",
+                sortForce: null,
+                sortAppend: null,
+                sortLocaleCompare: true,
+                textExtraction: "simple",
+                parsers: {}, widgets: [],
+                widgetZebra: {
+                    css: ["even", "odd"]
+                }, headers: {}, widthFixed: false,
+                cancelSelection: true,
+                sortList: [],
+                headerList: [],
+                dateFormat: "us",
+                decimal: '/\.|\,/g',
+                onRenderHeader: null,
+                selectorHeaders: 'thead th',
+                debug: false
+            };
+
+            /* debuging utils */
+
+            function benchmark(s, d) {
+                log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
+            }
+
+            this.benchmark = benchmark;
+
+            function log(s) {
+                if (typeof console != "undefined" && typeof console.debug != "undefined") {
+                    console.log(s);
+                } else {
+                    alert(s);
+                }
+            }
+
+            /* parsers utils */
+
+            function buildParserCache(table, $headers) {
+
+                if (table.config.debug) {
+                    var parsersDebug = "";
+                }
+
+                if (table.tBodies.length == 0) return; // In the case of empty tables
+                var rows = table.tBodies[0].rows;
+
+                if (rows[0]) {
+
+                    var list = [],
+                        cells = rows[0].cells,
+                        l = cells.length;
+
+                    for (var i = 0; i < l; i++) {
+
+                        var p = false;
+
+                        if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) {
+
+                            p = getParserById($($headers[i]).metadata().sorter);
+
+                        } else if ((table.config.headers[i] && table.config.headers[i].sorter)) {
+
+                            p = getParserById(table.config.headers[i].sorter);
+                        }
+                        if (!p) {
+
+                            p = detectParserForColumn(table, rows, -1, i);
+                        }
+
+                        if (table.config.debug) {
+                            parsersDebug += "column:" + i + " parser:" + p.id + "\n";
+                        }
+
+                        list.push(p);
+                    }
+                }
+
+                if (table.config.debug) {
+                    log(parsersDebug);
+                }
+
+                return list;
+            };
+
+            function detectParserForColumn(table, rows, rowIndex, cellIndex) {
+                var l = parsers.length,
+                    node = false,
+                    nodeValue = false,
+                    keepLooking = true;
+                while (nodeValue == '' && keepLooking) {
+                    rowIndex++;
+                    if (rows[rowIndex]) {
+                        node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex);
+                        nodeValue = trimAndGetNodeText(table.config, node);
+                        if (table.config.debug) {
+                            log('Checking if value was empty on row:' + rowIndex);
+                        }
+                    } else {
+                        keepLooking = false;
+                    }
+                }
+                for (var i = 1; i < l; i++) {
+                    if (parsers[i].is(nodeValue, table, node)) {
+                        return parsers[i];
+                    }
+                }
+                // 0 is always the generic parser (text)
+                return parsers[0];
+            }
+
+            function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) {
+                return rows[rowIndex].cells[cellIndex];
+            }
+
+            function trimAndGetNodeText(config, node) {
+                return $.trim(getElementText(config, node));
+            }
+
+            function getParserById(name) {
+                var l = parsers.length;
+                for (var i = 0; i < l; i++) {
+                    if (parsers[i].id.toLowerCase() == name.toLowerCase()) {
+                        return parsers[i];
+                    }
+                }
+                return false;
+            }
+
+            /* utils */
+
+            function buildCache(table) {
+
+                if (table.config.debug) {
+                    var cacheTime = new Date();
+                }
+
+                var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,
+                    totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,
+                    parsers = table.config.parsers,
+                    cache = {
+                        row: [],
+                        normalized: []
+                    };
+
+                for (var i = 0; i < totalRows; ++i) {
+
+                    /** Add the table data to main data array */
+                    var c = $(table.tBodies[0].rows[i]),
+                        cols = [];
+
+                    // if this is a child row, add it to the last row's children and
+                    // continue to the next row
+                    if (c.hasClass(table.config.cssChildRow)) {
+                        cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);
+                        // go to the next for loop
+                        continue;
+                    }
+
+                    cache.row.push(c);
+
+                    for (var j = 0; j < totalCells; ++j) {
+                        cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j]));
+                    }
+
+                    cols.push(cache.normalized.length); // add position for rowCache
+                    cache.normalized.push(cols);
+                    cols = null;
+                };
+
+                if (table.config.debug) {
+                    benchmark("Building cache for " + totalRows + " rows:", cacheTime);
+                }
+
+                return cache;
+            };
+
+            function getElementText(config, node) {
+
+                var text = "";
+
+                if (!node) return "";
+
+                if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false;
+
+                if (config.textExtraction == "simple") {
+                    if (config.supportsTextContent) {
+                        text = node.textContent;
+                    } else {
+                        if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) {
+                            text = node.childNodes[0].innerHTML;
+                        } else {
+                            text = node.innerHTML;
+                        }
+                    }
+                } else {
+                    if (typeof(config.textExtraction) == "function") {
+                        text = config.textExtraction(node);
+                    } else {
+                        text = $(node).text();
+                    }
+                }
+                return text;
+            }
+
+            function appendToTable(table, cache) {
+
+                if (table.config.debug) {
+                    var appendTime = new Date()
+                }
+
+                var c = cache,
+                    r = c.row,
+                    n = c.normalized,
+                    totalRows = n.length,
+                    checkCell = (n[0].length - 1),
+                    tableBody = $(table.tBodies[0]),
+                    rows = [];
+
+
+                for (var i = 0; i < totalRows; i++) {
+                    var pos = n[i][checkCell];
+
+                    rows.push(r[pos]);
+
+                    if (!table.config.appender) {
+
+                        //var o = ;
+                        var l = r[pos].length;
+                        for (var j = 0; j < l; j++) {
+                            tableBody[0].appendChild(r[pos][j]);
+                        }
+
+                        // 
+                    }
+                }
+
+
+
+                if (table.config.appender) {
+
+                    table.config.appender(table, rows);
+                }
+
+                rows = null;
+
+                if (table.config.debug) {
+                    benchmark("Rebuilt table:", appendTime);
+                }
+
+                // apply table widgets
+                applyWidget(table);
+
+                // trigger sortend
+                setTimeout(function () {
+                    $(table).trigger("sortEnd");
+                }, 0);
+
+            };
+
+            function buildHeaders(table) {
+
+                if (table.config.debug) {
+                    var time = new Date();
+                }
+
+                var meta = ($.metadata) ? true : false;
+                
+                var header_index = computeTableHeaderCellIndexes(table);
+
+                $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) {
+
+                    this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
+                    // this.column = index;
+                    this.order = formatSortingOrder(table.config.sortInitialOrder);
+                    
+					
+					this.count = this.order;
+
+                    if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true;
+					if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index);
+
+                    if (!this.sortDisabled) {
+                        var $th = $(this).addClass(table.config.cssHeader);
+                        if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th);
+                    }
+
+                    // add cell to headerList
+                    table.config.headerList[index] = this;
+                });
+
+                if (table.config.debug) {
+                    benchmark("Built headers:", time);
+                    log($tableHeaders);
+                }
+
+                return $tableHeaders;
+
+            };
+
+            // from:
+            // http://www.javascripttoolbox.com/lib/table/examples.php
+            // http://www.javascripttoolbox.com/temp/table_cellindex.html
+
+
+            function computeTableHeaderCellIndexes(t) {
+                var matrix = [];
+                var lookup = {};
+                var thead = t.getElementsByTagName('THEAD')[0];
+                var trs = thead.getElementsByTagName('TR');
+
+                for (var i = 0; i < trs.length; i++) {
+                    var cells = trs[i].cells;
+                    for (var j = 0; j < cells.length; j++) {
+                        var c = cells[j];
+
+                        var rowIndex = c.parentNode.rowIndex;
+                        var cellId = rowIndex + "-" + c.cellIndex;
+                        var rowSpan = c.rowSpan || 1;
+                        var colSpan = c.colSpan || 1
+                        var firstAvailCol;
+                        if (typeof(matrix[rowIndex]) == "undefined") {
+                            matrix[rowIndex] = [];
+                        }
+                        // Find first available column in the first row
+                        for (var k = 0; k < matrix[rowIndex].length + 1; k++) {
+                            if (typeof(matrix[rowIndex][k]) == "undefined") {
+                                firstAvailCol = k;
+                                break;
+                            }
+                        }
+                        lookup[cellId] = firstAvailCol;
+                        for (var k = rowIndex; k < rowIndex + rowSpan; k++) {
+                            if (typeof(matrix[k]) == "undefined") {
+                                matrix[k] = [];
+                            }
+                            var matrixrow = matrix[k];
+                            for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
+                                matrixrow[l] = "x";
+                            }
+                        }
+                    }
+                }
+                return lookup;
+            }
+
+            function checkCellColSpan(table, rows, row) {
+                var arr = [],
+                    r = table.tHead.rows,
+                    c = r[row].cells;
+
+                for (var i = 0; i < c.length; i++) {
+                    var cell = c[i];
+
+                    if (cell.colSpan > 1) {
+                        arr = arr.concat(checkCellColSpan(table, headerArr, row++));
+                    } else {
+                        if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) {
+                            arr.push(cell);
+                        }
+                        // headerArr[row] = (i+row);
+                    }
+                }
+                return arr;
+            };
+
+            function checkHeaderMetadata(cell) {
+                if (($.metadata) && ($(cell).metadata().sorter === false)) {
+                    return true;
+                };
+                return false;
+            }
+
+            function checkHeaderOptions(table, i) {
+                if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) {
+                    return true;
+                };
+                return false;
+            }
+			
+			 function checkHeaderOptionsSortingLocked(table, i) {
+                if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder;
+                return false;
+            }
+			
+            function applyWidget(table) {
+                var c = table.config.widgets;
+                var l = c.length;
+                for (var i = 0; i < l; i++) {
+
+                    getWidgetById(c[i]).format(table);
+                }
+
+            }
+
+            function getWidgetById(name) {
+                var l = widgets.length;
+                for (var i = 0; i < l; i++) {
+                    if (widgets[i].id.toLowerCase() == name.toLowerCase()) {
+                        return widgets[i];
+                    }
+                }
+            };
+
+            function formatSortingOrder(v) {
+                if (typeof(v) != "Number") {
+                    return (v.toLowerCase() == "desc") ? 1 : 0;
+                } else {
+                    return (v == 1) ? 1 : 0;
+                }
+            }
+
+            function isValueInArray(v, a) {
+                var l = a.length;
+                for (var i = 0; i < l; i++) {
+                    if (a[i][0] == v) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            function setHeadersCss(table, $headers, list, css) {
+                // remove all header information
+                $headers.removeClass(css[0]).removeClass(css[1]);
+
+                var h = [];
+                $headers.each(function (offset) {
+                    if (!this.sortDisabled) {
+                        h[this.column] = $(this);
+                    }
+                });
+
+                var l = list.length;
+                for (var i = 0; i < l; i++) {
+                    h[list[i][0]].addClass(css[list[i][1]]);
+                }
+            }
+
+            function fixColumnWidth(table, $headers) {
+                var c = table.config;
+                if (c.widthFixed) {
+                    var colgroup = $('<colgroup>');
+                    $("tr:first td", table.tBodies[0]).each(function () {
+                        colgroup.append($('<col>').css('width', $(this).width()));
+                    });
+                    $(table).prepend(colgroup);
+                };
+            }
+
+            function updateHeaderSortCount(table, sortList) {
+                var c = table.config,
+                    l = sortList.length;
+                for (var i = 0; i < l; i++) {
+                    var s = sortList[i],
+                        o = c.headerList[s[0]];
+                    o.count = s[1];
+                    o.count++;
+                }
+            }
+
+            /* sorting methods */
+
+            function multisort(table, sortList, cache) {
+
+                if (table.config.debug) {
+                    var sortTime = new Date();
+                }
+
+                var dynamicExp = "var sortWrapper = function(a,b) {",
+                    l = sortList.length;
+
+                // TODO: inline functions.
+                for (var i = 0; i < l; i++) {
+
+                    var c = sortList[i][0];
+                    var order = sortList[i][1];
+                    // var s = (getCachedSortType(table.config.parsers,c) == "text") ?
+                    // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ?
+                    // "sortNumeric" : "sortNumericDesc");
+                    // var s = (table.config.parsers[c].type == "text") ? ((order == 0)
+                    // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ?
+                    // makeSortNumeric(c) : makeSortNumericDesc(c));
+                    var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c));
+                    var e = "e" + i;
+
+                    dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c
+                    // + "]); ";
+                    dynamicExp += "if(" + e + ") { return " + e + "; } ";
+                    dynamicExp += "else { ";
+
+                }
+
+                // if value is the same keep orignal order
+                var orgOrderCol = cache.normalized[0].length - 1;
+                dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";
+
+                for (var i = 0; i < l; i++) {
+                    dynamicExp += "}; ";
+                }
+
+                dynamicExp += "return 0; ";
+                dynamicExp += "}; ";
+
+                if (table.config.debug) {
+                    benchmark("Evaling expression:" + dynamicExp, new Date());
+                }
+
+                eval(dynamicExp);
+
+                cache.normalized.sort(sortWrapper);
+
+                if (table.config.debug) {
+                    benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime);
+                }
+
+                return cache;
+            };
+
+            function makeSortFunction(type, direction, index) {
+                var a = "a[" + index + "]",
+                    b = "b[" + index + "]";
+                if (type == 'text' && direction == 'asc') {
+                    return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));";
+                } else if (type == 'text' && direction == 'desc') {
+                    return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));";
+                } else if (type == 'numeric' && direction == 'asc') {
+                    return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));";
+                } else if (type == 'numeric' && direction == 'desc') {
+                    return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));";
+                }
+            };
+
+            function makeSortText(i) {
+                return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));";
+            };
+
+            function makeSortTextDesc(i) {
+                return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));";
+            };
+
+            function makeSortNumeric(i) {
+                return "a[" + i + "]-b[" + i + "];";
+            };
+
+            function makeSortNumericDesc(i) {
+                return "b[" + i + "]-a[" + i + "];";
+            };
+
+            function sortText(a, b) {
+                if (table.config.sortLocaleCompare) return a.localeCompare(b);
+                return ((a < b) ? -1 : ((a > b) ? 1 : 0));
+            };
+
+            function sortTextDesc(a, b) {
+                if (table.config.sortLocaleCompare) return b.localeCompare(a);
+                return ((b < a) ? -1 : ((b > a) ? 1 : 0));
+            };
+
+            function sortNumeric(a, b) {
+                return a - b;
+            };
+
+            function sortNumericDesc(a, b) {
+                return b - a;
+            };
+
+            function getCachedSortType(parsers, i) {
+                return parsers[i].type;
+            }; /* public methods */
+            this.construct = function (settings) {
+                return this.each(function () {
+                    // if no thead or tbody quit.
+                    if (!this.tHead || !this.tBodies) return;
+                    // declare
+                    var $this, $document, $headers, cache, config, shiftDown = 0,
+                        sortOrder;
+                    // new blank config object
+                    this.config = {};
+                    // merge and extend.
+                    config = $.extend(this.config, $.tablesorter.defaults, settings);
+                    // store common expression for speed
+                    $this = $(this);
+                    // save the settings where they read
+                    $.data(this, "tablesorter", config);
+                    // build headers
+                    $headers = buildHeaders(this);
+                    // try to auto detect column type, and store in tables config
+                    this.config.parsers = buildParserCache(this, $headers);
+                    // build the cache for the tbody cells
+                    cache = buildCache(this);
+                    // get the css class names, could be done else where.
+                    var sortCSS = [config.cssDesc, config.cssAsc];
+                    // fixate columns if the users supplies the fixedWidth option
+                    fixColumnWidth(this);
+                    // apply event handling to headers
+                    // this is to big, perhaps break it out?
+                    $headers.click(
+
+                    function (e) {
+                        var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;
+                        if (!this.sortDisabled && totalRows > 0) {
+                            // Only call sortStart if sorting is
+                            // enabled.
+                            $this.trigger("sortStart");
+                            // store exp, for speed
+                            var $cell = $(this);
+                            // get current column index
+                            var i = this.column;
+                            // get current column sort order
+                            this.order = this.count++ % 2;
+							// always sort on the locked order.
+							if(this.lockedOrder) this.order = this.lockedOrder;
+							
+							// user only whants to sort on one
+                            // column
+                            if (!e[config.sortMultiSortKey]) {
+                                // flush the sort list
+                                config.sortList = [];
+                                if (config.sortForce != null) {
+                                    var a = config.sortForce;
+                                    for (var j = 0; j < a.length; j++) {
+                                        if (a[j][0] != i) {
+                                            config.sortList.push(a[j]);
+                                        }
+                                    }
+                                }
+                                // add column to sort list
+                                config.sortList.push([i, this.order]);
+                                // multi column sorting
+                            } else {
+                                // the user has clicked on an all
+                                // ready sortet column.
+                                if (isValueInArray(i, config.sortList)) {
+                                    // revers the sorting direction
+                                    // for all tables.
+                                    for (var j = 0; j < config.sortList.length; j++) {
+                                        var s = config.sortList[j],
+                                            o = config.headerList[s[0]];
+                                        if (s[0] == i) {
+                                            o.count = s[1];
+                                            o.count++;
+                                            s[1] = o.count % 2;
+                                        }
+                                    }
+                                } else {
+                                    // add column to sort list array
+                                    config.sortList.push([i, this.order]);
+                                }
+                            };
+                            setTimeout(function () {
+                                // set css for headers
+                                setHeadersCss($this[0], $headers, config.sortList, sortCSS);
+                                appendToTable(
+	                                $this[0], multisort(
+	                                $this[0], config.sortList, cache)
+								);
+                            }, 1);
+                            // stop normal event by returning false
+                            return false;
+                        }
+                        // cancel selection
+                    }).mousedown(function () {
+                        if (config.cancelSelection) {
+                            this.onselectstart = function () {
+                                return false
+                            };
+                            return false;
+                        }
+                    });
+                    // apply easy methods that trigger binded events
+                    $this.bind("update", function () {
+                        var me = this;
+                        setTimeout(function () {
+                            // rebuild parsers.
+                            me.config.parsers = buildParserCache(
+                            me, $headers);
+                            // rebuild the cache map
+                            cache = buildCache(me);
+                        }, 1);
+                    }).bind("updateCell", function (e, cell) {
+                        var config = this.config;
+                        // get position from the dom.
+                        var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex];
+                        // update cache
+                        cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(
+                        getElementText(config, cell), cell);
+                    }).bind("sorton", function (e, list) {
+                        $(this).trigger("sortStart");
+                        config.sortList = list;
+                        // update and store the sortlist
+                        var sortList = config.sortList;
+                        // update header count index
+                        updateHeaderSortCount(this, sortList);
+                        // set css for headers
+                        setHeadersCss(this, $headers, sortList, sortCSS);
+                        // sort the table and append it to the dom
+                        appendToTable(this, multisort(this, sortList, cache));
+                    }).bind("appendCache", function () {
+                        appendToTable(this, cache);
+                    }).bind("applyWidgetId", function (e, id) {
+                        getWidgetById(id).format(this);
+                    }).bind("applyWidgets", function () {
+                        // apply widgets
+                        applyWidget(this);
+                    });
+                    if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {
+                        config.sortList = $(this).metadata().sortlist;
+                    }
+                    // if user has supplied a sort list to constructor.
+                    if (config.sortList.length > 0) {
+                        $this.trigger("sorton", [config.sortList]);
+                    }
+                    // apply widgets
+                    applyWidget(this);
+                });
+            };
+            this.addParser = function (parser) {
+                var l = parsers.length,
+                    a = true;
+                for (var i = 0; i < l; i++) {
+                    if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {
+                        a = false;
+                    }
+                }
+                if (a) {
+                    parsers.push(parser);
+                };
+            };
+            this.addWidget = function (widget) {
+                widgets.push(widget);
+            };
+            this.formatFloat = function (s) {
+                var i = parseFloat(s);
+                return (isNaN(i)) ? 0 : i;
+            };
+            this.formatInt = function (s) {
+                var i = parseInt(s);
+                return (isNaN(i)) ? 0 : i;
+            };
+            this.isDigit = function (s, config) {
+                // replace all an wanted chars and match.
+                return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, '')));
+            };
+            this.clearTableBody = function (table) {
+                if ($.browser.msie) {
+                    function empty() {
+                        while (this.firstChild)
+                        this.removeChild(this.firstChild);
+                    }
+                    empty.apply(table.tBodies[0]);
+                } else {
+                    table.tBodies[0].innerHTML = "";
+                }
+            };
+        }
+    });
+
+    // extend plugin scope
+    $.fn.extend({
+        tablesorter: $.tablesorter.construct
+    });
+
+    // make shortcut
+    var ts = $.tablesorter;
+
+    // add default parsers
+    ts.addParser({
+        id: "text",
+        is: function (s) {
+            return true;
+        }, format: function (s) {
+            return $.trim(s.toLocaleLowerCase());
+        }, type: "text"
+    });
+
+    ts.addParser({
+        id: "digit",
+        is: function (s, table) {
+            var c = table.config;
+            return $.tablesorter.isDigit(s, c);
+        }, format: function (s) {
+            return $.tablesorter.formatFloat(s);
+        }, type: "numeric"
+    });
+
+    ts.addParser({
+        id: "currency",
+        is: function (s) {
+            return /^[£$€?.]/.test(s);
+        }, format: function (s) {
+            return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), ""));
+        }, type: "numeric"
+    });
+
+    ts.addParser({
+        id: "ipAddress",
+        is: function (s) {
+            return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);
+        }, format: function (s) {
+            var a = s.split("."),
+                r = "",
+                l = a.length;
+            for (var i = 0; i < l; i++) {
+                var item = a[i];
+                if (item.length == 2) {
+                    r += "0" + item;
+                } else {
+                    r += item;
+                }
+            }
+            return $.tablesorter.formatFloat(r);
+        }, type: "numeric"
+    });
+
+    ts.addParser({
+        id: "url",
+        is: function (s) {
+            return /^(https?|ftp|file):\/\/$/.test(s);
+        }, format: function (s) {
+            return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), ''));
+        }, type: "text"
+    });
+
+    ts.addParser({
+        id: "isoDate",
+        is: function (s) {
+            return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);
+        }, format: function (s) {
+            return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(
+            new RegExp(/-/g), "/")).getTime() : "0");
+        }, type: "numeric"
+    });
+
+    ts.addParser({
+        id: "percent",
+        is: function (s) {
+            return /\%$/.test($.trim(s));
+        }, format: function (s) {
+            return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), ""));
+        }, type: "numeric"
+    });
+
+    ts.addParser({
+        id: "usLongDate",
+        is: function (s) {
+            return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));
+        }, format: function (s) {
+            return $.tablesorter.formatFloat(new Date(s).getTime());
+        }, type: "numeric"
+    });
+
+    ts.addParser({
+        id: "shortDate",
+        is: function (s) {
+            return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);
+        }, format: function (s, table) {
+            var c = table.config;
+            s = s.replace(/\-/g, "/");
+            if (c.dateFormat == "us") {
+                // reformat the string in ISO format
+                s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2");
+            } else if (c.dateFormat == "uk") {
+                // reformat the string in ISO format
+                s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1");
+            } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") {
+                s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3");
+            }
+            return $.tablesorter.formatFloat(new Date(s).getTime());
+        }, type: "numeric"
+    });
+    ts.addParser({
+        id: "time",
+        is: function (s) {
+            return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);
+        }, format: function (s) {
+            return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime());
+        }, type: "numeric"
+    });
+    ts.addParser({
+        id: "metadata",
+        is: function (s) {
+            return false;
+        }, format: function (s, table, cell) {
+            var c = table.config,
+                p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
+            return $(cell).metadata()[p];
+        }, type: "numeric"
+    });
+    // add default widgets
+    ts.addWidget({
+        id: "zebra",
+        format: function (table) {
+            if (table.config.debug) {
+                var time = new Date();
+            }
+            var $tr, row = -1,
+                odd;
+            // loop through the visible rows
+            $("tr:visible", table.tBodies[0]).each(function (i) {
+                $tr = $(this);
+                // style children rows the same way the parent
+                // row was styled
+                if (!$tr.hasClass(table.config.cssChildRow)) row++;
+                odd = (row % 2 == 0);
+                $tr.removeClass(
+                table.config.widgetZebra.css[odd ? 0 : 1]).addClass(
+                table.config.widgetZebra.css[odd ? 1 : 0])
+            });
+            if (table.config.debug) {
+                $.tablesorter.benchmark("Applying Zebra widget", time);
+            }
+        }
+    });
+})(jQuery);
\ No newline at end of file

Added: incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/tables.js
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/tables.js?rev=1537021&view=auto
==============================================================================
--- incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/tables.js (added)
+++ incubator/sirona/trunk/reporting/src/main/resources/resources/js/tablesorter/tables.js Wed Oct 30 09:50:51 2013
@@ -0,0 +1,3 @@
+$(function() {
+  $("table").tablesorter({debug: true});
+});

Modified: incubator/sirona/trunk/reporting/src/main/resources/templates/home.vm
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/reporting/src/main/resources/templates/home.vm?rev=1537021&r1=1537020&r2=1537021&view=diff
==============================================================================
--- incubator/sirona/trunk/reporting/src/main/resources/templates/home.vm (original)
+++ incubator/sirona/trunk/reporting/src/main/resources/templates/home.vm Wed Oct 30 09:50:51 2013
@@ -17,12 +17,5 @@
 <h1>Home</h1>
 
 <div>
-    Welcome to the Commons Monitoring Webapp. here is the list of available pages:
+    A list of available page is on the left. Click on one of them to access the desired information.
 </div>
-<div>
-    <ul>
-        #foreach ( $plugin in $plugins )
-            <li><a href="$mapping$plugin.url">$plugin.name</a></li>
-        #end
-    </ul>
-</div>
\ No newline at end of file

Modified: incubator/sirona/trunk/reporting/src/main/resources/templates/page.vm
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/reporting/src/main/resources/templates/page.vm?rev=1537021&r1=1537020&r2=1537021&view=diff
==============================================================================
--- incubator/sirona/trunk/reporting/src/main/resources/templates/page.vm (original)
+++ incubator/sirona/trunk/reporting/src/main/resources/templates/page.vm Wed Oct 30 09:50:51 2013
@@ -15,51 +15,86 @@
   limitations under the License.
 *#
 <!DOCTYPE html>
-<html>
-  <head>
+<html lang="en">
+<head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="">
+    <meta name="author" content="">
 
-    <title>Monitoring HTML Report</title>
+    <title>Sirona</title>
 
-    <link rel="stylesheet" href="$mapping/resources/css/bootstrap.min.css">
-    <link rel="stylesheet" href="$mapping/resources/css/bootstrap-datetimepicker.min.css">
-    <link rel="stylesheet" href="$mapping/resources/css/monitoring.css">
-  </head>
-  <body>
-      <div class="navbar navbar-inverse">
-          <div class="navbar-inner">
-              <div class="container">
-                  <a class="brand" href="http://commons.apache.org/sandbox/commons-monitoring/">Commons Monitoring</a>
-                  <div class="nav-collapse">
-                      <ul class="nav">
-                          #if ( $templateId == "home")
-                            <li class="active">
-                          #else
-                            <li>
-                          #end
-                              <a href="$mapping/">Home</a>
-                          </li>
-                          #foreach ( $plugin in $plugins )
-                              #if ( $templateId == $plugin.name )
-                                <li class="active">
-                              #else
-                                <li>
-                              #end
-                                  <a href="$mapping$plugin.url">$plugin.name</a>
-                              </li>
-                          #end
-                      </ul>
-                      <ul class="nav pull-right">
-                          <li><a href="http://www.apache.org/">ASF</a></li>
-                      </ul>
-                  </div>
-              </div>
-          </div>
-      </div>
+    <!-- Bootstrap core CSS -->
+    <link href="$mapping/resources/css/bootstrap.css" rel="stylesheet">
 
-      <script type="text/javascript" src="$mapping/resources/js/jquery.min.js"></script>
+    <!-- Add custom CSS here -->
+    <link href="$mapping/resources/css/sb-admin.css" rel="stylesheet">
+    <link rel="stylesheet" href="$mapping/resources/font-awesome/css/font-awesome.min.css">
+    <link rel="stylesheet" href="$mapping/resources/css/sirona.css">
+    <!-- Page Specific CSS -->
+    <link rel="stylesheet" href="http://cdn.oesmith.co.uk/morris-0.4.3.min.css">
 
-      #parse( "/templates/$currentTemplate" )
-  </body>
+    <!-- js shared accross pages -->
+    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+</head>
+
+<body>
+
+<div id="wrapper">
+
+<!-- Sidebar -->
+<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+    <!-- Brand and toggle get grouped for better mobile display -->
+    <div class="navbar-header">
+        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
+            <span class="sr-only">Toggle navigation</span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+        </button>
+        <a class="navbar-brand" href="http://sirona.incubator.apache.org/">Sirona</a>
+    </div>
+
+    <!-- Collect the nav links, forms, and other content for toggling -->
+    <div class="collapse navbar-collapse navbar-ex1-collapse">
+        <ul class="nav navbar-nav side-nav" id="plugins">
+            #if ( $templateId == "home")
+            <li class="active">
+            #else
+            <li>
+            #end
+                <a href="$mapping/">Home</a>
+            </li>
+            #foreach ( $plugin in $plugins )
+                #if ( $templateId == $plugin.name )
+                <li class="active">
+                #else
+                <li>
+                #end
+                <a href="$mapping$plugin.url">$plugin.name</a>
+            </li>
+            #end
+        </ul>
+
+        <ul class="nav navbar-nav navbar-right navbar-user">
+            <li><a href="http://www.apache.org/">Apache Software Foundation</a></li>
+        </ul>
+    </div><!-- /.navbar-collapse -->
+</nav>
+
+<div id="page-wrapper">
+    #parse( "/templates/$currentTemplate" )
+</div><!-- /#page-wrapper -->
+
+</div><!-- /#wrapper -->
+
+<!-- Bootstrap core JavaScript -->
+<script src="$mapping/resources/js/bootstrap.js"></script>
+<!-- Page Specific Plugins -->
+<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
+<script src="http://cdn.oesmith.co.uk/morris-0.4.3.min.js"></script>
+<script src="$mapping/resources/js/morris/chart-data-morris.js"></script>
+<script src="$mapping/resources/js/tablesorter/jquery.tablesorter.js"></script>
+<script src="$mapping/resources/js/tablesorter/tables.js"></script>
+</body>
 </html>

Added: incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/HomeTest.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/HomeTest.java?rev=1537021&view=auto
==============================================================================
--- incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/HomeTest.java (added)
+++ incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/HomeTest.java Wed Oct 30 09:50:51 2013
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.sirona.reporting.template;
+
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class HomeTest extends SironaReportingTestBase {
+    @Test
+    public void checkHomeShowsPlugins() throws IOException {
+        final HtmlPage page = page("");
+        final String plugins = page.getElementById("plugins").asText();
+        assertThat(plugins, containsString("Home"));
+        assertThat(plugins, containsString("Report"));
+        assertThat(plugins, containsString("JMX"));
+        assertThat(plugins, containsString("JVM"));
+        assertThat(plugins, containsString("JTA"));
+        assertThat(plugins, containsString("Threads"));
+        assertThat(plugins, containsString("Web"));
+    }
+}

Copied: incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/ReportTest.java (from r1535949, incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/CounterDetailTemplateTest.java)
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/ReportTest.java?p2=incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/ReportTest.java&p1=incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/CounterDetailTemplateTest.java&r1=1535949&r2=1537021&rev=1537021&view=diff
==============================================================================
--- incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/CounterDetailTemplateTest.java (original)
+++ incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/ReportTest.java Wed Oct 30 09:50:51 2013
@@ -16,65 +16,26 @@
  */
 package org.apache.sirona.reporting.template;
 
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Locale;
-
-import javax.servlet.ServletContainerInitializer;
-
-import org.apache.catalina.startup.Constants;
+import com.gargoylesoftware.htmlunit.WebAssert;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
 import org.apache.sirona.Role;
 import org.apache.sirona.counters.Counter;
 import org.apache.sirona.counters.Unit;
-import org.apache.sirona.reporting.web.plugin.Plugin;
-import org.apache.sirona.reporting.web.plugin.report.ReportPlugin;
 import org.apache.sirona.reporting.web.plugin.report.format.MapFormat;
-import org.apache.sirona.reporting.web.registration.MonitoringReportingInitializer;
 import org.apache.sirona.repositories.Repository;
-import org.jboss.arquillian.container.test.api.Deployment;
-import org.jboss.arquillian.junit.Arquillian;
-import org.jboss.arquillian.test.api.ArquillianResource;
-import org.jboss.shrinkwrap.api.Archive;
-import org.jboss.shrinkwrap.api.ShrinkWrap;
-import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset;
-import org.jboss.shrinkwrap.api.spec.JavaArchive;
-import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
-import com.gargoylesoftware.htmlunit.WebAssert;
-import com.gargoylesoftware.htmlunit.WebClient;
-import com.gargoylesoftware.htmlunit.html.HtmlPage;
-
-@RunWith(Arquillian.class)
-public class CounterDetailTemplateTest {
-    @Deployment(testable = false)
-    public static Archive<?> war() { // note: we depend on tomcat embedded adapter since we don't add dependencies + we use the fact Repository.INSTANCE is in the same JVM
-        // we use this hack to init jars to skip since this method is triggered before any deployment
-        // so ContextConfig is not yet loaded
-        System.setProperty(Constants.DEFAULT_JARS_TO_SKIP, "a*,c*,d*,e*,g*,h*,i*,j*,l*,m*,n*,p*,r*,sa*,se*,sh*,su*,t*,v*,w*,x*,z*");
-
-        // real impl of this method starts here
-        return ShrinkWrap.create(WebArchive.class, "sirona-test.war")
-            .addPackages(true, "org.apache.sirona.reporting.web")
-            .addAsServiceProvider(Plugin.class, ReportPlugin.class)
-            .addAsWebInfResource(new ClassLoaderAsset("templates/report/report.vm"), "classes/templates/report/report.vm")
-            .addAsWebInfResource(new ClassLoaderAsset("templates/report/counter.vm"), "classes/templates/report/counter.vm")
-            .addAsWebInfResource(new ClassLoaderAsset("templates/macro.vm"), "classes/templates/macro.vm")
-            .addAsLibraries(
-                ShrinkWrap.create(JavaArchive.class, "sci.jar") // bug in tomcat?
-                    .addAsServiceProvider(ServletContainerInitializer.class, MonitoringReportingInitializer.class));
-    }
+import java.io.IOException;
+import java.util.Locale;
 
-    @ArquillianResource
-    private URL base;
+import static org.junit.Assert.assertEquals;
 
+public class ReportTest extends SironaReportingTestBase {
     private Counter.Key key;
     
     private static Locale oldLocale;
@@ -124,12 +85,4 @@ public class CounterDetailTemplateTest {
         assertEquals("Counter\tRole\tHits\tMax\tMean\tMin\tStandardDeviation\tSum\tVariance\tValue\tConcurrency\tMaxConcurrency" + lineSeparator +
             "counter\trole (u)\t1.00\t55.00\t55.00\t55.00\t0.00\t55.00\t0.00\t55.00\t0.00\t0.00", text);
     }
-
-    private static WebClient newClient() {
-        final WebClient webClient = new WebClient();
-        webClient.getOptions().setJavaScriptEnabled(false);
-        webClient.getOptions().setCssEnabled(false);
-        webClient.getOptions().setAppletEnabled(false);
-        return webClient;
-    }
 }

Added: incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/SironaReportingTestBase.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/SironaReportingTestBase.java?rev=1537021&view=auto
==============================================================================
--- incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/SironaReportingTestBase.java (added)
+++ incubator/sirona/trunk/reporting/src/test/java/org/apache/sirona/reporting/template/SironaReportingTestBase.java Wed Oct 30 09:50:51 2013
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.sirona.reporting.template;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import org.apache.catalina.startup.Constants;
+import org.apache.sirona.reporting.web.plugin.Plugin;
+import org.apache.sirona.reporting.web.plugin.report.ReportPlugin;
+import org.apache.sirona.reporting.web.registration.MonitoringReportingInitializer;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.Archive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.runner.RunWith;
+
+import javax.servlet.ServletContainerInitializer;
+import java.io.IOException;
+import java.net.URL;
+
+// NOTE: we depend implicitely on tomcat embedded adapter since
+// 1) we don't add dependencies
+// 2) we use the fact Repository.INSTANCE is in the same JVM
+@RunWith(Arquillian.class)
+public abstract class SironaReportingTestBase {
+    static {
+        // we use this hack to init jars to skip since this method is triggered before any deployment
+        // so ContextConfig is not yet loaded
+        System.setProperty(Constants.DEFAULT_JARS_TO_SKIP, "a*,c*,d*,e*,g*,h*,i*,j*,l*,m*,n*,p*,r*,sa*,se*,sh*,su*,t*,v*,w*,x*,z*");
+    }
+
+    @Deployment(testable = false)
+    public static Archive<?> war() {
+        // bug hack: we don't create an Archive<?> representing our webapp since all will be at classpath
+        // this makes test faster and easier to maintain (if we add stuff) but dependent on our current tomcat embedded adapter
+        return ShrinkWrap.create(WebArchive.class, "sirona-test.war")
+            .addAsLibraries(
+                ShrinkWrap.create(JavaArchive.class, "sci.jar") // bug in tomcat?
+                    .addAsServiceProvider(ServletContainerInitializer.class, MonitoringReportingInitializer.class));
+    }
+
+    @ArquillianResource
+    protected URL base;
+
+    protected HtmlPage page(final String path) throws IOException {
+        return newClient().getPage(base.toExternalForm() + "/monitoring/" + path);
+    }
+
+    protected static WebClient newClient() {
+        final WebClient webClient = new WebClient();
+        webClient.getOptions().setJavaScriptEnabled(false);
+        webClient.getOptions().setCssEnabled(false);
+        webClient.getOptions().setAppletEnabled(false);
+        return webClient;
+    }
+}

Modified: incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/registration/WebMonitoringInitializer.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/registration/WebMonitoringInitializer.java?rev=1537021&r1=1537020&r2=1537021&view=diff
==============================================================================
--- incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/registration/WebMonitoringInitializer.java (original)
+++ incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/registration/WebMonitoringInitializer.java Wed Oct 30 09:50:51 2013
@@ -46,22 +46,30 @@ public class WebMonitoringInitializer im
         }
 
         final String monStatus = Boolean.toString(!"false".equalsIgnoreCase(ctx.getInitParameter(MonitoringFilter.MONITOR_STATUS)));
+        String ignoredUrls = ctx.getInitParameter(MonitoringFilter.IGNORED_URLS);
         String monitoredUrls = ctx.getInitParameter(Configuration.CONFIG_PROPERTY_PREFIX + "web.monitored-urls");
         if (!"false".equalsIgnoreCase(monitoredUrls)) {
             if (monitoredUrls == null) {
                 monitoredUrls = "/*";
             }
+
+            if (ignoredUrls == null) {
+                ignoredUrls = Configuration.getProperty(MonitoringFilter.IGNORED_URLS, "/monitoring");
+            }
+
             if (monitoredUrls.contains(",")) {
                 final String[] split = monitoredUrls.split(",");
                 for (int i = 0; i < split.length; i++) {
                     final FilterRegistration.Dynamic filter = ctx.addFilter("monitoring-filter-" + i, MonitoringFilter.class);
                     filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, split[i]);
                     filter.setInitParameter(MonitoringFilter.MONITOR_STATUS, monStatus);
+                    filter.setInitParameter(MonitoringFilter.IGNORED_URLS, ignoredUrls);
                 }
             } else {
                 final FilterRegistration.Dynamic filter = ctx.addFilter("monitoring-filter", MonitoringFilter.class);
                 filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, monitoredUrls);
                 filter.setInitParameter(MonitoringFilter.MONITOR_STATUS, monStatus);
+                filter.setInitParameter(MonitoringFilter.IGNORED_URLS, ignoredUrls);
             }
         }
     }

Modified: incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/servlet/MonitoringFilter.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/servlet/MonitoringFilter.java?rev=1537021&r1=1537020&r2=1537021&view=diff
==============================================================================
--- incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/servlet/MonitoringFilter.java (original)
+++ incubator/sirona/trunk/web/src/main/java/org/apache/sirona/web/servlet/MonitoringFilter.java Wed Oct 30 09:50:51 2013
@@ -41,13 +41,21 @@ import java.util.concurrent.ConcurrentMa
 
 public class MonitoringFilter extends AbstractPerformanceInterceptor<MonitoringFilter.Invocation> implements Filter {
     public static final String MONITOR_STATUS = Configuration.CONFIG_PROPERTY_PREFIX + "web.monitored-status";
+    public static final String IGNORED_URLS = Configuration.CONFIG_PROPERTY_PREFIX + "web.ignored-urls";
 
     private static final ConcurrentMap<Integer, Counter.Key> STATUS_KEYS = new ConcurrentHashMap<Integer, Counter.Key>();
 
     private boolean monitorStatus;
 
+    private String[] ignored = new String[0];
+
     @Override
     public void init(final FilterConfig filterConfig) throws ServletException {
+        final String ignoredUrls = filterConfig.getInitParameter(IGNORED_URLS);
+        if (ignoredUrls != null) {
+            ignored = ignoredUrls.split(",");
+        }
+
         final String monStatus = filterConfig.getInitParameter(MONITOR_STATUS);
         monitorStatus = monStatus == null || "true".equalsIgnoreCase(monStatus);
 
@@ -69,6 +77,15 @@ public class MonitoringFilter extends Ab
     public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
         if (HttpServletRequest.class.isInstance(request)) {
             final HttpServletRequest httpRequest = HttpServletRequest.class.cast(request);
+
+            final String uri = getRequestedUri(httpRequest);
+            for (final String ignorable : ignored) {
+                if (uri.startsWith(ignorable)) {
+                    chain.doFilter(request, response);
+                    return;
+                }
+            }
+
             final HttpServletResponse httpResponse = HttpServletResponse.class.cast(response);
             try {
                 doInvoke(new Invocation(httpRequest, httpResponse, chain));
@@ -113,6 +130,15 @@ public class MonitoringFilter extends Ab
         // no-op
     }
 
+    protected static String getRequestedUri(final HttpServletRequest request) {
+        final String uri = request.getRequestURI();
+        final String context = request.getContextPath();
+        if (uri.length() <= context.length()) {
+            return uri;
+        }
+        return uri.substring(context.length());
+    }
+
     private static Counter.Key statusKey(final int status) {
         final Counter.Key key = STATUS_KEYS.get(status);
         if (key != null) {