/* * Statistics.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $ * * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts * Institute of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the Hewlett-Packard Company nor the name of the * Massachusetts Institute of Technology nor the names of their * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.app.xmlui.aspect.artifactbrowser; import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer; import org.dspace.app.xmlui.wing.element.*; import org.dspace.app.xmlui.wing.element.List; import org.dspace.app.xmlui.wing.WingException; import org.dspace.app.xmlui.wing.Message; import org.dspace.app.xmlui.utils.UIException; import org.dspace.app.xmlui.utils.DSpaceValidity; import org.dspace.app.statistics.*; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeManager; import org.dspace.core.ConfigurationManager; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.excalibur.source.SourceValidity; import org.apache.log4j.Logger; import org.xml.sax.SAXException; import java.io.*; import java.io.File; import java.sql.SQLException; import java.util.*; import java.util.regex.Pattern; import java.util.regex.Matcher; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.text.ParseException; /** * Transformer to display statistics data in XML UI. * * Unlike the JSP interface that pre-generates HTML and stores in the reports folder, * this class transforms the raw analysis data into a Wing representation */ public class StatisticsViewer extends AbstractDSpaceTransformer implements CacheableProcessingComponent { private final static Logger log = Logger.getLogger(StatisticsViewer.class); private final static Message T_dspace_home = message("xmlui.general.dspace_home"); private final static Message T_choose_report = message("xmlui.ArtifactBrowser.StatisticsViewer.choose_month"); private final static Message T_page_title = message("xmlui.ArtifactBrowser.StatisticsViewer.report.title"); private final static Message T_empty_title = message("xmlui.ArtifactBrowser.StatisticsViewer.no_report.title"); private final static Message T_empty_text = message("xmlui.ArtifactBrowser.StatisticsViewer.no_report.text"); private final static SimpleDateFormat sdfDisplay = new SimpleDateFormat("MM'/'yyyy"); private final static SimpleDateFormat sdfLink = new SimpleDateFormat("yyyy'-'M"); private boolean initialised = false; private String reportDate = null; private SourceValidity validity; /** * Get the caching key for this report * @return */ public Serializable getKey() { initialise(); if (reportDate != null) return reportDate; return "general"; } /** * Generate the validity for this cached entry * @return */ public SourceValidity getValidity() { if (validity == null) { try { initialise(); boolean showReport = ConfigurationManager.getBooleanProperty("report.public"); // If the report isn't public if (!showReport) { try { // Administrators can always view reports showReport = AuthorizeManager.isAdmin(context); } catch (SQLException sqle) { log.error("Unable to check for administrator", sqle); } } // Only generate a validity if the report is visible if (showReport) { File analysisFile = null; // Get a file for the report data if (reportDate != null) analysisFile = StatisticsLoader.getAnalysisFor(reportDate); else analysisFile = StatisticsLoader.getGeneralAnalysis(); if (analysisFile != null) { // Generate the validity based on the properties of the report data file DSpaceValidity newValidity = new DSpaceValidity(); newValidity.add(Long.toString(analysisFile.lastModified())); newValidity.add("-"); newValidity.add(Long.toString(analysisFile.length())); validity = newValidity.complete(); } } } catch (Exception e) { } } return validity; } /** * Add additional navigation options. This is to allow selection of a monthly report * * @param options * @throws SAXException * @throws WingException * @throws UIException * @throws SQLException * @throws IOException * @throws AuthorizeException */ public void addOptions(Options options) throws SAXException, WingException, UIException, SQLException, IOException, AuthorizeException { Date[] monthlyDates = StatisticsLoader.getMonthlyAnalysisDates(); if (monthlyDates != null && monthlyDates.length > 0) { List statList = options.addList("statsreports"); statList.setHead(T_choose_report); HashMap<String, String> params = new HashMap<String, String>(); for (Date date : monthlyDates) { params.put("date", sdfLink.format(date)); statList.addItemXref(super.generateURL("statistics", params), sdfDisplay.format(date)); } } } /** * Add title, etc. metadata * * @param pageMeta * @throws SAXException * @throws WingException * @throws UIException * @throws SQLException * @throws IOException * @throws AuthorizeException */ public void addPageMeta(PageMeta pageMeta) throws SAXException, WingException, UIException, SQLException, IOException, AuthorizeException { initialise(); pageMeta.addMetadata("title").addContent(T_page_title); pageMeta.addTrailLink(contextPath + "/", T_dspace_home); pageMeta.addTrail().addContent(T_page_title); } /** * Output the body of the report * * @param body * @throws SAXException * @throws WingException * @throws UIException * @throws SQLException * @throws IOException * @throws AuthorizeException */ public void addBody(Body body) throws SAXException, WingException, UIException, SQLException, IOException, AuthorizeException { initialise(); boolean publicise = ConfigurationManager.getBooleanProperty("report.public"); // Check that the reports are either public, or user is an administrator if (!publicise && !AuthorizeManager.isAdmin(context)) throw new AuthorizeException(); // Retrieve the report data to display File analysisFile; if (reportDate != null) analysisFile = StatisticsLoader.getAnalysisFor(reportDate); else analysisFile = StatisticsLoader.getGeneralAnalysis(); // Create the renderer for the results Division div = body.addDivision("statistics", "primary"); if (analysisFile != null) { try { // Generate the XML stream Report myRep = new XMLUIReport(div); ReportGenerator.processReport(context, myRep, analysisFile.getCanonicalPath()); } catch (Exception e) { throw new UIException(e); } } else { div.setHead(T_empty_title); div.addPara(T_empty_text); } } /** * Initialise the member variables from the request */ private void initialise() { if (!initialised) { Request request = ObjectModelHelper.getRequest(objectModel); reportDate = (String) request.getParameter("date"); initialised = true; } } /** * Clear the member variables so that the instance can be reused */ public void recycle() { initialised = false; reportDate = null; validity = null; super.recycle(); } /** * Implementation of the Report interface, to output the statistics data for xmlui * Note that all methods that return Strings return 'null' in this implementation, as * all the outputting is done directly using the Wing framework. */ class XMLUIReport implements Report { private ArrayList<Statistics> blocks = new ArrayList<Statistics>(); private String mainTitle = null; private String pageTitle = null; /** start date for report */ private Date start = null; /** end date for report */ private Date end = null; private Division rootDiv; private Division currDiv; /** * Hide the default constructor, so that you have to pass in a Division */ private XMLUIReport() {} /** * Create instance, providing the Wing element that we will be adding to * * @param myDiv */ public XMLUIReport(Division myDiv) { rootDiv = myDiv; currDiv = myDiv; } /** * Get the header for the report - currently not supported * * @return */ public String header() { return header(""); } // Currently not supported public String header(String title) { return ""; } /** * Add the main title to the report * @return */ public String mainTitle() { try { rootDiv.setHead(mainTitle); } catch (WingException we) { log.error("Error creating XML for report", we); } return null; } /** * Output the date range for this report * @return */ public String dateRange() { StringBuilder content = new StringBuilder(); DateFormat df = DateFormat.getDateInstance(); if (start != null) { content.append(df.format(start)); } else { content.append("from start of records "); } content.append(" to "); if (end != null) { content.append(df.format(end)); } else { content.append(" end of records"); } try { rootDiv.addDivision("reportDate").addPara(content.toString()); } catch (WingException we) { log.error("Error creating XML for report", we); } return null; } /** * Output the section header * @param title * @return */ public String sectionHeader(String title) { try { currDiv.setHead(title); } catch (WingException we) { log.error("Error creating XML for report", we); } return null; } /** * Output the current statistics block * @param content * @return */ public String statBlock(Statistics content) { Stat[] stats = content.getStats(); try { int rows = stats.length; if (content.getStatName() != null || content.getResultName() != null) rows++; Table block = currDiv.addTable("reportBlock", rows, 2); // prepare the table headers if (content.getStatName() != null || content.getResultName() != null) { Row row = block.addRow(); if (content.getStatName() != null) { row.addCellContent(content.getStatName()); } else { row.addCellContent(" "); } if (content.getResultName() != null) { row.addCellContent(content.getResultName()); } else { row.addCellContent(" "); } } // output the statistics in the table for (int i = 0; i < stats.length; i++) { Row row = block.addRow(); if (stats[i].getReference() != null) { row.addCell().addXref(stats[i].getReference()).addContent(label(stats[i].getKey())); } else { row.addCell().addContent(label(stats[i].getKey())); } if (stats[i].getUnits() != null) { row.addCell(null, null, "right").addContent(entry(stats[i].getValue() + " " + stats[i].getUnits())); } else { row.addCell(null, null, "right").addContent(entry(ReportTools.numberFormat(stats[i].getValue()))); } } } catch (WingException we) { log.error("Error creating XML for report", we); } return null; } /** * Output any information about the lower boundary restriction for this section * @param floor * @return */ public String floorInfo(int floor) { try { if (floor > 0) { currDiv.addDivision("reportFloor").addPara("(more than " + ReportTools.numberFormat(floor) + " times)"); } } catch (WingException we) { log.error("Error creating XML for report", we); } return null; } /** * Output an explanation for this section * * @param explanation * @return */ public String blockExplanation(String explanation) { try { if (explanation != null) { currDiv.addDivision("reportExplanation").addPara(explanation); } } catch (WingException we) { log.error("Error creating XML for report", we); } return null; } /** * Output the footer * * @return */ public String footer() { return ""; } /** * Set the main title for this report * * @param name * @param serverName */ public void setMainTitle(String name, String serverName) { mainTitle = "Statistics for " + name; if (ConfigurationManager.getBooleanProperty("report.show.server", true)) mainTitle += " on " + serverName; if (pageTitle == null) { pageTitle = mainTitle; } return; } /** * Add a block to report on * * @param stat */ public void addBlock(Statistics stat) { blocks.add(stat); return; } /** * Render the statistics into an XML stream * @return */ public String render() { Pattern space = Pattern.compile(" "); // Output the heading information header(pageTitle); mainTitle(); dateRange(); // Loop through all the sections for (Statistics stats : blocks) { // navigation(); try { String title = stats.getSectionHeader(); String aName = title.toLowerCase(); Matcher matchSpace = space.matcher(aName); aName = matchSpace.replaceAll("_"); // Create a new division for each section currDiv = rootDiv.addDivision(aName); sectionHeader(title); // topLink(); blockExplanation(stats.getExplanation()); floorInfo(stats.getFloor()); statBlock(stats); currDiv = rootDiv; } catch (WingException we) { log.error("Error creating XML for report", we); } } return null; } /** * Set the start date for this report * @param start */ public void setStartDate(Date start) { this.start = start; } /** * Set the end date for this report * @param end */ public void setEndDate(Date end) { this.end = end; } } /** * Protect the display from excessively wrong data, typically this occures if a long word finds it's way * into the data that is not breakable by the browser because there is no space, dash, period, or other * delimiter character. This just prevents the page from blowing up when bad data is being presented. */ private static final int MAX_ENTRY_LENGTH = 50; private static String entry(String entry) { if (entry != null && entry.length() > MAX_ENTRY_LENGTH) entry = entry.substring(0,MAX_ENTRY_LENGTH-3) + "..."; return entry; } private static final int MAX_LABEL_LENGTH = 100; private static String label(String label) { if (label != null && label.length() > MAX_LABEL_LENGTH) label = label.substring(0,MAX_LABEL_LENGTH-3) + "..."; return label; } }