integrated from branch 1.5

Statistics viewer for XMLUI, based on existing DStat. Note that this generates the view from the analysis files (.dat), does not require HTML report generation.

git-svn-id: http://scm.dspace.org/svn/repo/trunk@3102 9c30dcfa-912a-0410-8fc2-9e0234be79fd
This commit is contained in:
Graham Triggs
2008-09-09 12:45:34 +00:00
parent c95de97f0a
commit e2985e2ecd
10 changed files with 1366 additions and 345 deletions

View File

@@ -44,6 +44,7 @@ import org.dspace.app.statistics.Report;
import org.dspace.app.statistics.Stat; import org.dspace.app.statistics.Stat;
import org.dspace.app.statistics.Statistics; import org.dspace.app.statistics.Statistics;
import org.dspace.app.statistics.ReportTools; import org.dspace.app.statistics.ReportTools;
import org.dspace.core.ConfigurationManager;
import java.text.DateFormat; import java.text.DateFormat;
@@ -54,13 +55,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.io.*;
/** /**
* This class provides HTML reports for the ReportGenerator class * This class provides HTML reports for the ReportGenerator class
* *
* @author Richard Jones * @author Richard Jones
*/ */
public class HTMLReport extends Report public class HTMLReport implements Report
{ {
// FIXME: all of these methods should do some content escaping before // FIXME: all of these methods should do some content escaping before
// outputting anything // outputting anything
@@ -80,6 +82,10 @@ public class HTMLReport extends Report
/** end date for report */ /** end date for report */
private Date end = null; private Date end = null;
/** the output file to which to write aggregation data */
private static String output = ConfigurationManager.getProperty("dspace.dir") +
File.separator + "log" + File.separator + "report";
/** /**
* constructor for HTML reporting * constructor for HTML reporting
*/ */
@@ -88,6 +94,14 @@ public class HTMLReport extends Report
// empty constructor // empty constructor
} }
public void setOutput(String newOutput)
{
if (newOutput != null)
{
output = newOutput;
}
}
/** /**
* return a string containing the report as generated by this class * return a string containing the report as generated by this class
* *
@@ -120,6 +134,23 @@ public class HTMLReport extends Report
// output the footer and return // output the footer and return
frag.append(footer()); frag.append(footer());
// NB: HTMLReport now takes responsibility to write the output file,
// so that the Report/ReportGenerator can have more general usage
// finally write the string into the output file
try
{
FileOutputStream fos = new FileOutputStream(output);
OutputStreamWriter osr = new OutputStreamWriter(fos, "UTF-8");
PrintWriter out = new PrintWriter(osr);
out.write(frag.toString());
out.close();
}
catch (IOException e)
{
System.out.println("Unable to write to output file " + output);
System.exit(0);
}
return frag.toString(); return frag.toString();
} }

View File

@@ -48,30 +48,18 @@ import java.util.Date;
import java.util.List; import java.util.List;
/** /**
* Abstract class to define an interface to a generic report generating * Sn interface to a generic report generating
* class, and to provide the polymorphism necessary to allow the report * class, and to provide the polymorphism necessary to allow the report
* generator to generate any number of different formats of report * generator to generate any number of different formats of report
* *
* Note: This used to be an abstract class, but has been made an interface as there wasn't
* any logic contained within it. It's also been made public, so that you can create a Report
* type without monkeying about in the statistics package.
*
* @author Richard Jones * @author Richard Jones
*/ */
abstract class Report public interface Report
{ {
/** a list of the statistics blocks being managed by this class */
private List blocks = new ArrayList();
/** the title for the page */
private String pageTitle = null;
/** the main title for the page */
private String mainTitle = null;
/** start date for report */
private Date start = null;
/** end date for report */
private Date end = null;
/** /**
* output any top headers that this page needs * output any top headers that this page needs
* *

View File

@@ -70,6 +70,7 @@ import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.sql.SQLException;
/** /**
* This class performs the action of coordinating a usage report being * This class performs the action of coordinating a usage report being
@@ -172,16 +173,9 @@ public class ReportGenerator
// report generator config data // report generator config data
//////////////// ////////////////
/** the format of the report to be output */
private static String format = null;
/** the input file to build the report from */ /** the input file to build the report from */
private static String input = null; private static String input = null;
/** the output file to which to write aggregation data */
private static String output = ConfigurationManager.getProperty("dspace.dir") +
File.separator + "log" + File.separator + "report";
/** the log file action to human readable action map */ /** the log file action to human readable action map */
private static String map = ConfigurationManager.getProperty("dspace.dir") + private static String map = ConfigurationManager.getProperty("dspace.dir") +
File.separator + "config" + File.separator + "dstat.map"; File.separator + "config" + File.separator + "dstat.map";
@@ -240,6 +234,9 @@ public class ReportGenerator
* aggregation data and output a file containing the report in the * aggregation data and output a file containing the report in the
* requested format * requested format
* *
* this method is retained for backwards compatibility, but delegates the actual
* wprk to a new method
*
* @param context the DSpace context in which this action is performed * @param context the DSpace context in which this action is performed
* @param myFormat the desired output format (currently on HTML supported) * @param myFormat the desired output format (currently on HTML supported)
* @param myInput the aggregation file to be turned into a report * @param myInput the aggregation file to be turned into a report
@@ -249,6 +246,32 @@ public class ReportGenerator
String myInput, String myOutput, String myInput, String myOutput,
String myMap) String myMap)
throws Exception throws Exception
{
// create the relevant report type
// FIXME: at the moment we only support HTML report generation
Report report = null;
if (myFormat.equals("html"))
{
report = new HTMLReport();
((HTMLReport)report).setOutput(myOutput);
}
if (myMap != null)
{
map = myMap;
}
ReportGenerator.processReport(context, report, myInput);
}
/**
* using the pre-configuration information passed here, read in the
* aggregation data and output a file containing the report in the
* requested format
*/
public static void processReport(Context context, Report report,
String myInput)
throws Exception, SQLException
{ {
startTime = new GregorianCalendar(); startTime = new GregorianCalendar();
@@ -264,7 +287,7 @@ public class ReportGenerator
generalSummary = new ArrayList(); generalSummary = new ArrayList();
// set the parameters for this analysis // set the parameters for this analysis
setParameters(myFormat, myInput, myOutput, myMap); setParameters(myInput);
// pre prepare our standard file readers and buffered readers // pre prepare our standard file readers and buffered readers
FileReader fr = null; FileReader fr = null;
@@ -276,14 +299,6 @@ public class ReportGenerator
// load the log file action to human readable action map // load the log file action to human readable action map
readMap(map); readMap(map);
// create the relevant report type
// FIXME: at the moment we only support HTML report generation
Report report = null;
if (format.equals("html"))
{
report = new HTMLReport();
}
report.setStartDate(startDate); report.setStartDate(startDate);
report.setEndDate(endDate); report.setEndDate(endDate);
report.setMainTitle(name, serverName); report.setMainTitle(name, serverName);
@@ -352,7 +367,8 @@ public class ReportGenerator
String info = null; String info = null;
for (i = 0; i < items.length; i++) for (i = 0; i < items.length; i++)
{ {
if (i < itemLookup) // Allow negative value to say that all items should be looked up
if (itemLookup < 0 || i < itemLookup)
{ {
info = getItemInfo(context, items[i].getKey()); info = getItemInfo(context, items[i].getKey());
} }
@@ -460,20 +476,7 @@ public class ReportGenerator
report.addBlock(process); report.addBlock(process);
// finally write the string into the output file report.render();
try
{
FileOutputStream fos = new FileOutputStream(output);
OutputStreamWriter osr = new OutputStreamWriter(fos, "UTF-8");
PrintWriter out = new PrintWriter(osr);
out.write(report.render());
out.close();
}
catch (IOException e)
{
System.out.println("Unable to write to output file " + output);
System.exit(0);
}
return; return;
} }
@@ -595,34 +598,15 @@ public class ReportGenerator
* the command line with args or calling the processReport method statically * the command line with args or calling the processReport method statically
* from elsewhere * from elsewhere
* *
* @param myFormat the log file directory to be analysed
* @param myInput regex for log file names * @param myInput regex for log file names
* @param myOutput config file to use for dstat
* @param myMap the action map file to use for translations
*/ */
public static void setParameters(String myFormat, String myInput, public static void setParameters(String myInput)
String myOutput, String myMap)
{ {
if (myFormat != null)
{
format = myFormat;
}
if (myInput != null) if (myInput != null)
{ {
input = myInput; input = myInput;
} }
if (myOutput != null)
{
output = myOutput;
}
if (myMap != null)
{
map = myMap;
}
return; return;
} }
@@ -650,9 +634,13 @@ public class ReportGenerator
catch (IOException e) catch (IOException e)
{ {
System.out.println("Failed to read input file: " + input); System.out.println("Failed to read input file: " + input);
System.exit(0); return;
} }
// first initialise a date format object to do our date processing
// if necessary
SimpleDateFormat sdf = new SimpleDateFormat("dd'/'MM'/'yyyy");
// FIXME: although this works, it is not very elegant // FIXME: although this works, it is not very elegant
// loop through the aggregator file and read in the values // loop through the aggregator file and read in the values
while ((record = br.readLine()) != null) while ((record = br.readLine()) != null)
@@ -699,64 +687,48 @@ public class ReportGenerator
} }
// if the line is real, then we carry on // if the line is real, then we carry on
// first initialise a date format object to do our date processing
// if necessary
SimpleDateFormat sdf = new SimpleDateFormat("dd'/'MM'/'yyyy");
// read the analysis contents in // read the analysis contents in
if (section.equals("archive")) if (section.equals("archive"))
{ {
archiveStats.put(key, value); archiveStats.put(key, value);
} }
else if (section.equals("action"))
if (section.equals("action"))
{ {
actionAggregator.put(key, value); actionAggregator.put(key, value);
} }
else if (section.equals("user"))
if (section.equals("user"))
{ {
userAggregator.put(key, value); userAggregator.put(key, value);
} }
else if (section.equals("search"))
if (section.equals("search"))
{ {
searchAggregator.put(key, value); searchAggregator.put(key, value);
} }
else if (section.equals("item"))
if (section.equals("item"))
{ {
itemAggregator.put(key, value); itemAggregator.put(key, value);
} }
else if (section.equals("user_email"))
// read the config details used to make this report in
if (section.equals("user_email"))
{ {
userEmail = value; userEmail = value;
} }
else if (section.equals("item_floor"))
if (section.equals("item_floor"))
{ {
itemFloor = Integer.parseInt(value); itemFloor = Integer.parseInt(value);
} }
else if (section.equals("search_floor"))
if (section.equals("search_floor"))
{ {
searchFloor = Integer.parseInt(value); searchFloor = Integer.parseInt(value);
} }
else if (section.equals("host_url"))
if (section.equals("host_url"))
{ {
url = value; url = value;
} }
else if (section.equals("item_lookup"))
if (section.equals("item_lookup"))
{ {
itemLookup = Integer.parseInt(value); itemLookup = Integer.parseInt(value);
} }
else if (section.equals("avg_item_views"))
if (section.equals("avg_item_views"))
{ {
try try
{ {
@@ -767,43 +739,35 @@ public class ReportGenerator
avgItemViews = 0; avgItemViews = 0;
} }
} }
else if (section.equals("server_name"))
if (section.equals("server_name"))
{ {
serverName = value; serverName = value;
} }
else if (section.equals("service_name"))
if (section.equals("service_name"))
{ {
name = value; name = value;
} }
else if (section.equals("start_date"))
if (section.equals("start_date"))
{ {
startDate = sdf.parse(value); startDate = sdf.parse(value);
} }
else if (section.equals("end_date"))
if (section.equals("end_date"))
{ {
endDate = sdf.parse(value); endDate = sdf.parse(value);
} }
else if (section.equals("analysis_process_time"))
if (section.equals("analysis_process_time"))
{ {
processTime = Integer.parseInt(value); processTime = Integer.parseInt(value);
} }
else if (section.equals("general_summary"))
if (section.equals("general_summary"))
{ {
generalSummary.add(value); generalSummary.add(value);
} }
else if (section.equals("log_lines"))
if (section.equals("log_lines"))
{ {
logLines = Integer.parseInt(value); logLines = Integer.parseInt(value);
} }
else if (section.equals("warnings"))
if (section.equals("warnings"))
{ {
warnings = Integer.parseInt(value); warnings = Integer.parseInt(value);
} }

View File

@@ -0,0 +1,397 @@
/*
* StatisticsLoader.java
*
* Version: $Revision: 1.0 $
*
* Date: $Date: 2007/08/28 10:00:00 $
*
* 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.statistics;
import org.apache.commons.lang.time.DateUtils;
import org.dspace.core.ConfigurationManager;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.File;
import java.io.FilenameFilter;
import java.text.SimpleDateFormat;
import java.text.ParseException;
/**
* Helper class for loading the analysis / report files from the reports directory
*/
public class StatisticsLoader
{
private static Map<String, StatsFile> monthlyAnalysis = null;
private static Map<String, StatsFile> monthlyReports = null;
private static StatsFile generalAnalysis = null;
private static StatsFile generalReport = null;
private static Date lastLoaded = null;
private static int fileCount = 0;
private static Pattern analysisMonthlyPattern;
private static Pattern analysisGeneralPattern;
private static Pattern reportMonthlyPattern;
private static Pattern reportGeneralPattern;
private static SimpleDateFormat monthlySDF;
private static SimpleDateFormat generalSDF;
// one time initialisation of the regex patterns and formatters we will use
static
{
analysisMonthlyPattern = Pattern.compile("dspace-log-monthly-([0-9][0-9][0-9][0-9]-[0-9]+)\\.dat");
analysisGeneralPattern = Pattern.compile("dspace-log-general-([0-9]+-[0-9]+-[0-9]+)\\.dat");
reportMonthlyPattern = Pattern.compile("report-([0-9][0-9][0-9][0-9]-[0-9]+)\\.html");
reportGeneralPattern = Pattern.compile("report-general-([0-9]+-[0-9]+-[0-9]+)\\.html");
monthlySDF = new SimpleDateFormat("yyyy'-'M");
generalSDF = new SimpleDateFormat("yyyy'-'M'-'dd");
}
/**
* Get an array of the dates of the report files
* @return
*/
public static Date[] getMonthlyReportDates()
{
return sortDatesDescending(getDatesFromMap(monthlyReports));
}
/**
* Get an array of the dates of the analysis files
* @return
*/
public static Date[] getMonthlyAnalysisDates()
{
return sortDatesDescending(getDatesFromMap(monthlyAnalysis));
}
/**
* Convert the formatted dates that are the keys of the map into a date array
* @param monthlyMap
* @return
*/
protected static Date[] getDatesFromMap(Map<String, StatsFile> monthlyMap)
{
Set<String> keys = monthlyMap.keySet();
Date[] dates = new Date[keys.size()];
int i = 0;
for (String date : keys)
{
try
{
dates[i] = monthlySDF.parse(date);
}
catch (ParseException pe)
{
}
i++;
}
return dates;
}
/**
* Sort the date array in descending (reverse chronological) order
* @param dates
* @return
*/
protected static Date[] sortDatesDescending(Date[] dates)
{
Arrays.sort(dates, new Comparator<Date>() {
SimpleDateFormat sdf = monthlySDF;
public int compare(Date d1, Date d2)
{
if (d1 == null && d2 == null)
return 0;
else if (d1 == null)
return -1;
else if (d2 == null)
return 1;
else if (d1.before(d2))
return 1;
else if (d2.before(d1))
return -1;
return 0;
}
});
return dates;
}
/**
* Get the analysis file for a given date
* @param date
* @return
*/
public static File getAnalysisFor(String date)
{
StatisticsLoader.syncFileList();
StatsFile sf = (monthlyAnalysis == null ? null : monthlyAnalysis.get(date));
return sf == null ? null : sf.file;
}
/**
* Get the report file for a given date
* @param date
* @return
*/
public static File getReportFor(String date)
{
StatisticsLoader.syncFileList();
StatsFile sf = (monthlyReports == null ? null : monthlyReports.get(date));
return sf == null ? null : sf.file;
}
/**
* Get the current general analysis file
* @return
*/
public static File getGeneralAnalysis()
{
StatisticsLoader.syncFileList();
return generalAnalysis == null ? null : generalAnalysis.file;
}
/**
* Get the current general report file
* @return
*/
public static File getGeneralReport()
{
StatisticsLoader.syncFileList();
return generalReport == null ? null : generalReport.file;
}
/**
* Syncronize the cached list of analysis / report files with the reports directory
*
* We synchronize if:
*
* 1) The number of files is different (ie. files have been added or removed)
* 2) We haven't cached anything yet
* 3) The cache was last generate over an hour ago
*/
private static void syncFileList()
{
// Get an array of all the analysis and report files present
File[] fileList = StatisticsLoader.getAnalysisAndReportFileList();
if (fileList != null && fileList.length != fileCount)
StatisticsLoader.loadFileList(fileList);
else if (lastLoaded == null)
StatisticsLoader.loadFileList(fileList);
else if (DateUtils.addHours(lastLoaded, 1).before(new Date()))
StatisticsLoader.loadFileList(fileList);
}
/**
* Generate the cached file list from the array of files
* @param fileList
*/
private static synchronized void loadFileList(File[] fileList)
{
// If we haven't been passed an array of files, get one now
if (fileList == null || fileList.length == 0)
{
fileList = StatisticsLoader.getAnalysisAndReportFileList();
}
// Create new maps for the monthly analyis / reports
Map<String, StatsFile> newMonthlyAnalysis = new HashMap<String, StatsFile>();
Map<String, StatsFile> newMonthlyReports = new HashMap<String, StatsFile>();
StatsFile newGeneralAnalysis = null;
StatsFile newGeneralReport = null;
if (fileList != null)
{
for (File thisFile : fileList)
{
StatsFile statsFile = null;
// If we haven't identified this file yet
if (statsFile == null)
{
// See if it is a monthly analysis file
statsFile = makeStatsFile(thisFile, analysisMonthlyPattern, monthlySDF);
if (statsFile != null)
{
// If it is, add it to the map
newMonthlyAnalysis.put(statsFile.dateStr, statsFile);
}
}
// If we haven't identified this file yet
if (statsFile == null)
{
// See if it is a monthly report file
statsFile = makeStatsFile(thisFile, reportMonthlyPattern, monthlySDF);
if (statsFile != null)
{
// If it is, add it to the map
newMonthlyReports.put(statsFile.dateStr, statsFile);
}
}
// If we haven't identified this file yet
if (statsFile == null)
{
// See if it is a general analysis file
statsFile = makeStatsFile(thisFile, analysisGeneralPattern, generalSDF);
if (statsFile != null)
{
// If it is, ensure that we are pointing to the most recent file
if (newGeneralAnalysis == null || statsFile.date.after(newGeneralAnalysis.date))
{
newGeneralAnalysis = statsFile;
}
}
}
// If we haven't identified this file yet
if (statsFile == null)
{
// See if it is a general report file
statsFile = makeStatsFile(thisFile, reportGeneralPattern, generalSDF);
if (statsFile != null)
{
// If it is, ensure that we are pointing to the most recent file
if (newGeneralReport == null || statsFile.date.after(newGeneralReport.date))
{
newGeneralReport = statsFile;
}
}
}
}
}
// Store the newly discovered values in the member cache
monthlyAnalysis = newMonthlyAnalysis;
monthlyReports = newMonthlyReports;
generalAnalysis = newGeneralAnalysis;
generalReport = newGeneralReport;
lastLoaded = new Date();
}
/**
* Generate a StatsFile entry for this file. The pattern and date formatters are used to
* identify the file as a particular type, and extract the relevant information.
* If the file is not identified by the formatter provided, then we return null
* @param thisFile
* @param thisPattern
* @param sdf
* @return
*/
private static StatsFile makeStatsFile(File thisFile, Pattern thisPattern, SimpleDateFormat sdf)
{
Matcher matcher = thisPattern.matcher(thisFile.getName());
if (matcher.matches())
{
StatsFile sf = new StatsFile();
sf.file = thisFile;
sf.path = thisFile.getPath();
sf.dateStr = matcher.group(1).trim();
try
{
sf.date = sdf.parse(sf.dateStr);
}
catch (ParseException e)
{
}
return sf;
}
return null;
}
/**
* Get an array of all the analysis and report files
* @return
*/
private static File[] getAnalysisAndReportFileList()
{
File reportDir = new File(ConfigurationManager.getProperty("report.dir"));
if (reportDir != null)
{
return reportDir.listFiles(new AnalysisAndReportFilter());
}
return null;
}
/**
* Simple class for holding information about an analysis/report file
*/
private static class StatsFile
{
File file;
String path;
Date date;
String dateStr;
}
/**
* Filter used to restrict files in the reports directory to just analysis or report types
*/
private static class AnalysisAndReportFilter implements FilenameFilter
{
public boolean accept(File dir, String name)
{
if (analysisMonthlyPattern.matcher(name).matches())
return true;
if (analysisGeneralPattern.matcher(name).matches())
return true;
if (reportMonthlyPattern.matcher(name).matches())
return true;
if (reportGeneralPattern.matcher(name).matches())
return true;
return false;
}
}
}

View File

@@ -109,6 +109,8 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
private static final Message T_context_export_community = message("xmlui.administrative.Navigation.context_export_community"); private static final Message T_context_export_community = message("xmlui.administrative.Navigation.context_export_community");
private static final Message T_account_export = message("xmlui.administrative.Navigation.account_export"); private static final Message T_account_export = message("xmlui.administrative.Navigation.account_export");
private static final Message T_statistics = message("xmlui.administrative.Navigation.statistics");
/** exports available for download */ /** exports available for download */
java.util.List<String> availableExports = null; java.util.List<String> availableExports = null;
@@ -303,6 +305,7 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
admin.addItemXref(contextPath+"/admin/item", T_administrative_items); admin.addItemXref(contextPath+"/admin/item", T_administrative_items);
admin.addItemXref(contextPath+"/admin/withdrawn", T_administrative_withdrawn); admin.addItemXref(contextPath+"/admin/withdrawn", T_administrative_withdrawn);
admin.addItemXref(contextPath+"/admin/panel", T_administrative_control_panel); admin.addItemXref(contextPath+"/admin/panel", T_administrative_control_panel);
admin.addItemXref(contextPath+"/statistics", T_statistics);
} }
} }

View File

@@ -88,18 +88,6 @@ public class Navigation extends AbstractDSpaceTransformer implements CacheablePr
private static final Message T_communities_and_collections = private static final Message T_communities_and_collections =
message("xmlui.ArtifactBrowser.Navigation.communities_and_collections"); message("xmlui.ArtifactBrowser.Navigation.communities_and_collections");
private static final Message T_browse_titles =
message("xmlui.ArtifactBrowser.Navigation.browse_titles");
private static final Message T_browse_authors =
message("xmlui.ArtifactBrowser.Navigation.browse_authors");
private static final Message T_browse_subjects =
message("xmlui.ArtifactBrowser.Navigation.browse_subjects");
private static final Message T_browse_dates =
message("xmlui.ArtifactBrowser.Navigation.browse_dates");
private static final Message T_head_this_collection = private static final Message T_head_this_collection =
message("xmlui.ArtifactBrowser.Navigation.head_this_collection"); message("xmlui.ArtifactBrowser.Navigation.head_this_collection");

View File

@@ -0,0 +1,640 @@
/*
* Statistics.java
*
* Version: $Revision: 1.0 $
*
* Date: $Date: 2007/08/28 10:00:00 $
*
* 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("&nbsp;");
}
if (content.getResultName() != null)
{
row.addCellContent(content.getResultName());
}
else
{
row.addCellContent("&nbsp;");
}
}
// 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(stats[i].getKey());
}
else
{
row.addCell().addContent(stats[i].getKey());
}
if (stats[i].getUnits() != null)
{
row.addCell(null, null, "right").addContent(stats[i].getValue() + " " + stats[i].getUnits());
}
else
{
row.addCell(null, null, "right").addContent(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;
}
}
}

View File

@@ -64,9 +64,9 @@ and searching the repository.
<map:transformer name="Contact" src="org.dspace.app.xmlui.aspect.artifactbrowser.Contact"/> <map:transformer name="Contact" src="org.dspace.app.xmlui.aspect.artifactbrowser.Contact"/>
<map:transformer name="RestrictedItem" src="org.dspace.app.xmlui.aspect.artifactbrowser.RestrictedItem"/> <map:transformer name="RestrictedItem" src="org.dspace.app.xmlui.aspect.artifactbrowser.RestrictedItem"/>
<map:transformer name="FrontPageSearch" src="org.dspace.app.xmlui.aspect.artifactbrowser.FrontPageSearch"/> <map:transformer name="FrontPageSearch" src="org.dspace.app.xmlui.aspect.artifactbrowser.FrontPageSearch"/>
<map:transformer name="Statistics" src="org.dspace.app.xmlui.aspect.artifactbrowser.StatisticsViewer"/>
</map:transformers> </map:transformers>
<map:matchers default="wildcard"> <map:matchers default="wildcard">
<map:matcher name="HandleTypeMatcher" src="org.dspace.app.xmlui.aspect.general.HandleTypeMatcher"/> <map:matcher name="HandleTypeMatcher" src="org.dspace.app.xmlui.aspect.general.HandleTypeMatcher"/>
<map:matcher name="HandleAuthorizedMatcher" src="org.dspace.app.xmlui.aspect.general.HandleAuthorizedMatcher"/> <map:matcher name="HandleAuthorizedMatcher" src="org.dspace.app.xmlui.aspect.general.HandleAuthorizedMatcher"/>
@@ -244,6 +244,12 @@ and searching the repository.
<!-- Handle specific features (legacy) --> <!-- Handle specific features (legacy) -->
<map:match pattern="handle/*/**"> <map:match pattern="handle/*/**">
<!-- Display statistics -->
<map:match pattern="statistics">
<map:transform type="Statistics"/>
<map:serialize type="xml"/>
</map:match>
<!-- Inform the user that the item they are viewing is a restricted resource --> <!-- Inform the user that the item they are viewing is a restricted resource -->
<map:match pattern="handle/*/*/restricted-resource"> <map:match pattern="handle/*/*/restricted-resource">
<map:transform type="RestrictedItem"/> <map:transform type="RestrictedItem"/>

View File

@@ -490,6 +490,10 @@ td {
color: inherit; color: inherit;
} }
td.ds-table-cell.right {
text-align: right;
}
*.first-cell { *.first-cell {
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;