// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software Foundation;
// either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this program;
// if not, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: TestHsqlPersistenceManagerStats.java,v 1.6 2007/10/04 08:49:53 spyromus Exp $
//
package com.salas.bb.persistence.backend;
import com.salas.bb.domain.*;
import com.salas.bb.persistence.PersistenceException;
import com.salas.bb.persistence.domain.CountStats;
import com.salas.bb.persistence.domain.VisitStats;
import com.salas.bb.utils.Constants;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
/**
* This suite contains tests for <code>HsqlPersistenceManager</code> unit.
*/
public class TestHsqlPersistenceManagerStats extends AbstractHsqlPersistenceTestCase
{
private static final int SLEEP_TIME = 100;
private static final int MS_RANGE = 2000;
protected DirectFeed feed;
protected StandardGuide guide;
protected long creationTime;
@Override
protected void setUp() throws Exception
{
super.setUp();
initManager("/resources");
// Create a feed
feed = new DirectFeed();
feed.setXmlURL(new URL("http://localhost/"));
creationTime = System.currentTimeMillis();
// Create a guide
guide = new StandardGuide();
guide.setTitle("1");
guide.add(feed);
// Insert a guide
pm.insertGuide(guide, 0);
}
/**
* Checks if the stats table record is created.
*
* @throws PersistenceException exception.
* @throws SQLException exception.
*/
public void testAddGuide()
throws PersistenceException, SQLException
{
// Check if the row is there
VisitStats stats = getGuideVisitStats(guide.getID());
assertNotNull(stats);
assertEquals(0, stats.getCountTotal());
assertEquals(0, stats.getCountReset());
assertTrue("Failed range check. initTime = " + stats.getInitTime() + ", now=" + creationTime,
Math.abs(stats.getInitTime() - creationTime) < MS_RANGE);
assertTrue("Failed range check. resetTime = " + stats.getResetTime() + ", now=" + creationTime,
Math.abs(stats.getResetTime() - creationTime) < MS_RANGE);
}
/**
* Checks if the stats table record is created.
*
* @throws PersistenceException exception.
* @throws SQLException exception.
*/
public void testAddFeed()
throws PersistenceException, SQLException
{
// Check if the row is there
VisitStats stats = getFeedVisitStats(feed.getID());
assertNotNull(stats);
assertEquals(0, stats.getCountTotal());
assertEquals(0, stats.getCountReset());
assertTrue("Failed range check. initTime = " + stats.getInitTime() + ", now=" + creationTime,
Math.abs(stats.getInitTime() - creationTime) < MS_RANGE);
assertTrue("Failed range check. resetTime = " + stats.getResetTime() + ", now=" + creationTime,
Math.abs(stats.getResetTime() - creationTime) < MS_RANGE);
}
/** Checks visiting a guide. */
public void testVisitGuide()
{
// Visit a guide
pm.guideVisited(guide);
// Check
VisitStats stats = getGuideVisitStats(guide.getID());
assertEquals(guide.getTitle(), stats.getObjectTitle());
assertEquals(1, stats.getCountTotal());
assertEquals(1, stats.getCountReset());
}
/** Checks visiting a transient guide. */
public void testVisitTransientGuide()
{
// Create a non-saved guide
StandardGuide guide2 = new StandardGuide();
guide2.setTitle("2");
// Visit a guide -- should be no error
pm.guideVisited(guide2);
}
/** Checks visiting a feed. */
public void testVisitFeed()
{
// Visit a feed
pm.feedVisited(feed);
// Check
VisitStats stats = getFeedVisitStats(feed.getID());
assertEquals(feed.getTitle(), stats.getObjectTitle());
assertEquals(1, stats.getCountTotal());
assertEquals(1, stats.getCountReset());
}
/** Visiting a transient feed. */
public void testVisitTransientFeed()
{
// Create a non-saved feed
DirectFeed feed2 = new DirectFeed();
// Visit a feed -- should be no error
pm.feedVisited(feed2);
}
/**
* Recording hour and day stats.
*
* @throws PersistenceException exception.
*
* @throws SQLException if database fails.
*/
public void testReadStats()
throws PersistenceException, SQLException
{
PreparedStatement stmt;
// Check the number of hour records in the database
stmt = pm.getPreparedStatement("SELECT COUNT(*) FROM READSTATS_HOUR");
ResultSet rs = stmt.executeQuery();
assertTrue(rs.next());
assertEquals(Constants.HOURS_IN_DAY, rs.getInt(1));
// Check the number of day records in the database
stmt = pm.getPreparedStatement("SELECT COUNT(*) FROM READSTATS_DAY");
rs = stmt.executeQuery();
assertTrue(rs.next());
assertEquals(Constants.DAYS_IN_WEEK, rs.getInt(1));
// Get current hour and day
Calendar cal = new GregorianCalendar();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int day = cal.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY;
// Read it
StandardArticle article = new StandardArticle("");
feed.appendArticle(article);
pm.articlesRead(null, null, 1);
// Check hours
CountStats[] stats = pm.getItemsReadPerHour();
assertEquals(Constants.HOURS_IN_DAY, stats.length);
for (int i = 0; i < Constants.HOURS_IN_DAY; i++)
{
assertEquals(i == hour ? 1 : 0, stats[i].getCountTotal());
assertEquals(i == hour ? 1 : 0, stats[i].getCountReset());
}
// Check days
stats = pm.getItemsReadPerWeekday();
assertEquals(Constants.DAYS_IN_WEEK, stats.length);
for (int i = 0; i < Constants.DAYS_IN_WEEK; i++)
{
assertEquals(i == day ? 1 : 0, stats[i].getCountTotal());
assertEquals(i == day ? 1 : 0, stats[i].getCountReset());
}
}
/**
* Reporting top 3 most visited guides.
*
* @throws PersistenceException exception.
* @throws InterruptedException if sleeping is interrupted.
*/
public void testTopGuideVisits()
throws PersistenceException, InterruptedException
{
// Create 3 more guides and visit them
StandardGuide guide2 = addAndVisitGuide("2", 2);
StandardGuide guide3 = addAndVisitGuide("3", 4);
StandardGuide guide4 = addAndVisitGuide("4", 1);
// Wait a little
sleep();
// Get 3 most visited guides
List<VisitStats> list = pm.getMostVisitedGuides(3);
assertEquals(3, list.size());
assertVisitStats(guide3.getID(), 4, 4, list.get(0));
assertVisitStats(guide2.getID(), 2, 2, list.get(1));
assertVisitStats(guide4.getID(), 1, 1, list.get(2));
}
/**
* Reporting top 3 most visited feeds.
*
* @throws PersistenceException exception.
* @throws MalformedURLException exception.
* @throws InterruptedException if interrupted.
*/
public void testTopFeedVisits()
throws MalformedURLException, PersistenceException, InterruptedException
{
// Create 3 more feeds and visit them
DirectFeed feed2 = addAndVisitFeed("2", 2);
DirectFeed feed3 = addAndVisitFeed("2", 4);
DirectFeed feed4 = addAndVisitFeed("2", 1);
// Wait a little
sleep();
// Get 3 most visited feeds
List<VisitStats> list = pm.getMostVisitedFeeds(3);
assertEquals(3, list.size());
assertVisitStats(feed3.getID(), 4, 4, list.get(0));
assertVisitStats(feed2.getID(), 2, 2, list.get(1));
assertVisitStats(feed4.getID(), 1, 1, list.get(2));
}
/**
* Reseting time.
*
* @throws SQLException if database fails.
* @throws InterruptedException if interrupted.
*/
public void testResetTime()
throws SQLException, InterruptedException
{
long reset1 = getResetTime();
// Sleep a bit
sleep();
pm.reset();
long reset2 = getResetTime();
assertFalse(reset1 == reset2);
}
/**
* Reseting visit stats.
*/
public void testResetVisits()
{
// Visit guides and feeds
visit(guide, 2);
visit(feed, 2);
// Reset
pm.reset();
// Visit more
visit(guide, 2);
visit(feed, 2);
// Check
assertVisitStats(guide.getID(), 4, 2, getGuideVisitStats(guide.getID()));
assertVisitStats(feed.getID(), 4, 2, getFeedVisitStats(feed.getID()));
}
/**
* Reseting reading stats.
*
* @throws PersistenceException if fails to query records from database.
*/
public void testResetReadStats()
throws PersistenceException
{
// Get current hour and day
Calendar cal = new GregorianCalendar();
int hour = cal.get(Calendar.HOUR_OF_DAY);
int day = cal.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY;
// Create a sample article
StandardArticle article = new StandardArticle("");
feed.appendArticle(article);
// Mark two articles
pm.articlesRead(null, null, 1);
pm.articlesRead(null, null, 1);
// Reset
pm.reset();
// Mark two more articles
pm.articlesRead(null, null, 1);
pm.articlesRead(null, null, 1);
// Check Read per hour
CountStats[] stats = pm.getItemsReadPerHour();
for (int i = 0; i < Constants.HOURS_IN_DAY; i++)
{
assertEquals(i == hour ? 4 : 0, stats[i].getCountTotal());
assertEquals(i == hour ? 2 : 0, stats[i].getCountReset());
}
// Check Read per weekday
stats = pm.getItemsReadPerWeekday();
for (int i = 0; i < Constants.DAYS_IN_WEEK; i++)
{
assertEquals(i == day ? 4 : 0, stats[i].getCountTotal());
assertEquals(i == day ? 2 : 0, stats[i].getCountReset());
}
// Check Read history
}
/**
* Returns guide stats by given guide id.
*
* @param id ID.
*
* @return stats or <code>NULL</code> if row isn't there.
*/
private VisitStats getGuideVisitStats(long id)
{
VisitStats stats = null;
try
{
Statement stmt = pm.getConnection().createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT GS.*, G.TITLE TITLE " +
"FROM GUIDESTATS GS, GUIDES G " +
"WHERE G.ID=GUIDEID AND GUIDEID = " + id);
if (rs.next())
{
stats = new VisitStats((int)id,
rs.getString("TITLE"),
rs.getLong("COUNT_TOTAL"),
rs.getLong("COUNT_RESET"),
rs.getLong("INIT_TIME"),
rs.getLong("RESET_TIME"));
}
} catch (SQLException e)
{
e.printStackTrace();
fail("Failed to fetch the guide stats.");
}
return stats;
}
/**
* Returns feed stats by given guide ID.
*
* @param id ID.
*
* @return stats or <code>NULL</code> if row isn't there.
*/
private VisitStats getFeedVisitStats(long id)
{
VisitStats stats = null;
try
{
Statement stmt = pm.getConnection().createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT FS.*, COALESCE(DF.TITLE, DF.XMLURL, QF.TITLE, SF.TITLE) TITLE " +
"FROM FEEDSTATS FS LEFT JOIN DIRECTFEEDS DF ON DF.FEEDID=FS.FEEDID " +
"LEFT JOIN QUERYFEEDS QF ON QF.FEEDID=FS.FEEDID " +
"LEFT JOIN SEARCHFEEDS SF ON SF.FEEDID=FS.FEEDID " +
"WHERE FEEDID = " + id);
if (rs.next())
{
stats = new VisitStats((int)id,
rs.getString("TITLE"),
rs.getLong("COUNT_TOTAL"),
rs.getLong("COUNT_RESET"),
rs.getLong("INIT_TIME"),
rs.getLong("RESET_TIME"));
}
} catch (SQLException e)
{
e.printStackTrace();
fail("Failed to fetch the feed stats.");
}
return stats;
}
/**
* Create, add and visit guide.
*
* @param title guide title.
* @param visitsCount visit count.
*
* @return guide.
*
* @throws PersistenceException exception.
*/
private StandardGuide addAndVisitGuide(String title, int visitsCount)
throws PersistenceException
{
StandardGuide guide = new StandardGuide();
guide.setTitle(title);
pm.insertGuide(guide, 0);
visit(guide, visitsCount);
return guide;
}
/**
* Visit guide.
*
* @param guide guide.
* @param visitCount visit count.
*/
private void visit(IGuide guide, int visitCount)
{
for (int i = 0; i < visitCount; i++) pm.guideVisited(guide);
}
/**
* Create, add and visit feed.
*
* @param title feed title.
* @param visitsCount visit count.
*
* @return feed.
*
* @throws PersistenceException exception.
* @throws MalformedURLException exception.
*/
private DirectFeed addAndVisitFeed(String title, int visitsCount)
throws PersistenceException, MalformedURLException
{
DirectFeed feed = new DirectFeed();
feed.setXmlURL(new URL("http://localhost/" + title));
guide.add(feed);
pm.insertFeed(feed);
visit(feed, visitsCount);
return feed;
}
/**
* Visit feed.
*
* @param feed feed.
* @param visitCount visit count.
*/
private void visit(IFeed feed, int visitCount)
{
for (int i = 0; i < visitCount; i++) pm.feedVisited(feed);
}
/**
* Checks if visit stats are right.
*
* @param id object ID.
* @param visitTotal visit total.
* @param visitReset visit since reset.
* @param visit target visit stats.
*/
protected static void assertVisitStats(long id, int visitTotal, int visitReset, VisitStats visit)
{
assertEquals(id, visit.getObjectId());
assertEquals(visitTotal, visit.getCountTotal());
assertEquals(visitReset, visit.getCountReset());
}
/**
* Gets the reset time.
*
* @return reset time.
*
* @throws SQLException if database fails.
*/
protected long getResetTime()
throws SQLException
{
PreparedStatement stmt = pm.getPreparedStatement(
"SELECT value FROM APP_PROPERTIES WHERE name = 'statsResetTime'");
ResultSet rs = stmt.executeQuery();
rs.next();
return Long.parseLong(rs.getString(1));
}
/**
* Sleeps a bit to make stats stand appart.
*
* @throws InterruptedException in case interrupted.
*/
protected static void sleep()
throws InterruptedException
{
Thread.sleep(SLEEP_TIME);
}
}