// 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: HsqlArticlesPM.java,v 1.19 2008/02/27 08:35:59 spyromus Exp $
//
package com.salas.bb.persistence.backend;
import com.salas.bb.domain.IArticle;
import com.salas.bb.domain.IFeed;
import com.salas.bb.domain.IGuide;
import com.salas.bb.domain.StandardArticle;
import com.salas.bb.utils.i18n.Strings;
import java.net.URL;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* HSQL articles persistence manager.
*/
final class HsqlArticlesPM
{
private static final Logger LOG = Logger.getLogger(HsqlArticlesPM.class.getName());
/** Persistence manager context. */
private final HsqlPersistenceManager context;
/** Unsupported article type. */
private static final String MSG_UNSUPPORTED_TYPE = Strings.error("db.unsupported.article.type");
/** Article is not in database yet. */
private static final String MSG_NOT_IN_DB = Strings.error("db.article.is.not.in.database");
/** Article parameter is NULL. */
private static final String MSG_SHOULD_BE_SPECIFIED = Strings.error("unspecified.article");
/** Article is already in database. */
private static final String MSG_ALREADY_IN_DB = Strings.error("db.article.is.already.in.database");
/** Article is not assigned to some feed. */
private static final String MSG_NO_FEED = Strings.error("db.article.is.not.assigned.to.any.feed");
/** The feed article is assigned to is transient. */
private static final String MSG_TRANSIENT_FEED = Strings.error("db.article.is.assigned.to.transient.feed");
/**
* Created manager.
*
* @param aContext context to communicate to.
*/
public HsqlArticlesPM(HsqlPersistenceManager aContext)
{
context = aContext;
}
/**
* Inserts the article in database.
*
* @param article article to insert.
*
* @throws NullPointerException if article isn't specified.
* @throws IllegalStateException if article is already in database, or
* article isn't assigned to feed, or
* feed this article is assigned to is transient.
* @throws IllegalArgumentException if article is of unsupported type.
* @throws SQLException if database operation fails.
*/
public void insertArticle(IArticle article)
throws SQLException
{
if (article == null) throw new NullPointerException(MSG_SHOULD_BE_SPECIFIED);
if (article.getID() != -1L) throw new IllegalStateException(MSG_ALREADY_IN_DB);
IFeed feed = article.getFeed();
if (feed == null) throw new IllegalStateException(MSG_NO_FEED);
if (feed.getID() == -1L) throw new IllegalStateException(MSG_TRANSIENT_FEED);
if (!(article instanceof StandardArticle))
throw new IllegalArgumentException(MSG_UNSUPPORTED_TYPE);
StandardArticle standardArticle = (StandardArticle)article;
PreparedStatement stmt = context.getPreparedStatement(
"INSERT INTO ARTICLES (AUTHOR, TEXT, PLAINTEXT, SIMPLEMATCHKEY, PUBLICATIONDATE, TITLE, " +
"SUBJECT, READ, PINNED, LINK, FEEDID) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
try
{
stmt.setString(1, standardArticle.getAuthor());
stmt.setString(2, standardArticle.getText());
stmt.setString(3, standardArticle.getPlainText());
stmt.setString(4, standardArticle.getSimpleMatchKey());
Date publicationDate = standardArticle.getPublicationDate();
stmt.setLong(5, publicationDate == null ? -1L : publicationDate.getTime());
stmt.setString(6, standardArticle.getTitle());
stmt.setString(7, standardArticle.getSubject());
stmt.setBoolean(8, standardArticle.isRead());
stmt.setBoolean(9, standardArticle.isPinned());
URL link = standardArticle.getLink();
stmt.setString(10, link == null ? null : link.toString());
stmt.setLong(11, feed.getID());
stmt.executeUpdate();
// Get ID
long id = context.getInsertedID();
article.setID(id);
// Add a properties record
stmt = context.getPreparedStatement(
"INSERT INTO ARTICLE_PROPERTIES (ARTICLEID, POSITIVE_SENTIMENTS, NEGATIVE_SENTIMENTS) " +
"VALUES (?, ?, ?)");
stmt.setLong(1, id);
stmt.setInt(2, article.getPositiveSentimentsCount());
stmt.setInt(3, article.getNegativeSentimentsCount());
stmt.executeUpdate();
} finally
{
stmt.close();
}
}
/**
* Removes article from database.
*
* @param article article to remove.
*
* @throws NullPointerException if article isn't specified.
* @throws IllegalStateException if article is not in database.
* @throws SQLException if database operation fails.
*/
public void removeArticle(IArticle article)
throws SQLException
{
if (article == null) throw new NullPointerException(MSG_SHOULD_BE_SPECIFIED);
if (article.getID() == -1L)
{
LOG.log(Level.SEVERE, MessageFormat.format(Strings.error("0.title.1"),
MSG_NOT_IN_DB, article.getTitle()), new Exception("Dump"));
return;
}
PreparedStatement stmt = context.getPreparedStatement("DELETE FROM ARTICLES WHERE ID=?");
try
{
stmt.setLong(1, article.getID());
int rows = stmt.executeUpdate();
if (rows == 0)
{
IFeed feed = article.getFeed();
IGuide guide = null;
if (feed != null)
{
IGuide[] guides = feed.getParentGuides();
guide = guides.length == 0 ? null : guides[0];
}
String feedId = feed == null ? "no feed" : Long.toString(feed.getID());
String guideId = guide == null ? "no guide" : Long.toString(guide.getID());
throw new SQLException(MessageFormat.format(
Strings.error("db.hsql.removed.0.rows.for.articleid.0.feedid.1.guideid.2"),
article.getID(), feedId, guideId));
}
article.setID(-1L);
} finally
{
stmt.close();
}
}
/**
* Updates article in database.
*
* @param article article to update.
*
* @throws NullPointerException if article isn't specified.
* @throws IllegalArgumentException if article is of unsupported type.
* @throws SQLException if database operation fails.
*/
public void updateArticle(IArticle article)
throws SQLException
{
if (!checkArticle(article)) return;
// NOTE: We intentionally don't update text and plaintext fields because
// a) they never change
// b) there's the deadlock between LazyArticle.getPlainText() and AbstractArticle.setRead()
// being called from different threads
StandardArticle standardArticle = (StandardArticle)article;
PreparedStatement stmt = context.getPreparedStatement("UPDATE ARTICLES SET " +
"AUTHOR=?, SIMPLEMATCHKEY=?, PUBLICATIONDATE=?, TITLE=?, SUBJECT=?, READ=?," +
"PINNED=?, LINK=? WHERE ID=?");
try
{
stmt.setString(1, standardArticle.getAuthor());
stmt.setString(2, standardArticle.getSimpleMatchKey());
Date publicationDate = standardArticle.getPublicationDate();
stmt.setLong(3, publicationDate == null ? -1L : publicationDate.getTime());
stmt.setString(4, standardArticle.getTitle());
stmt.setString(5, standardArticle.getSubject());
stmt.setBoolean(6, standardArticle.isRead());
stmt.setBoolean(7, standardArticle.isPinned());
URL link = standardArticle.getLink();
stmt.setString(8, link == null ? null : link.toString());
stmt.setLong(9, article.getID());
int rows = stmt.executeUpdate();
if (rows == 0)
{
IFeed feed = article.getFeed();
IGuide[] guides = feed.getParentGuides();
IGuide guide = guides.length == 0 ? null : guides[0];
String feedId = feed == null ? "no feed" : Long.toString(feed.getID());
String guideId = guide == null ? "no guide" : Long.toString(guide.getID());
LOG.log(Level.SEVERE, MessageFormat.format(
Strings.error("db.hsql.updated.0.rows.for.articleid.0.feedid.1.guideid.2"),
article.getID(), feedId, guideId));
}
} finally
{
stmt.close();
}
}
/**
* Updates article in database.
*
* @param article article to update.
*
* @throws NullPointerException if article isn't specified.
* @throws IllegalArgumentException if article is of unsupported type.
* @throws SQLException if database operation fails.
*/
public void updateArticleProperties(IArticle article)
throws SQLException
{
if (!checkArticle(article)) return;
PreparedStatement stmt = context.getPreparedStatement("UPDATE ARTICLE_PROPERTIES SET " +
"POSITIVE_SENTIMENTS = ?, NEGATIVE_SENTIMENTS = ? " +
"WHERE ARTICLEID = ?");
try
{
stmt.setInt(1, article.getPositiveSentimentsCount());
stmt.setInt(2, article.getNegativeSentimentsCount());
stmt.setLong(3, article.getID());
stmt.executeUpdate();
} finally
{
stmt.close();
}
}
/**
* Checks an article for being present, being of a valid type etc.
*
* @param article article.
*
* @return TRUE if all well.
*
* @throws NullPointerException if article isn't specified.
* @throws IllegalArgumentException if article is of unsupported type.
* @throws SQLException if database operation fails.
*/
private static boolean checkArticle(IArticle article)
{
if (article == null) throw new NullPointerException(MSG_SHOULD_BE_SPECIFIED);
if (!(article instanceof StandardArticle)) throw new IllegalArgumentException(MSG_UNSUPPORTED_TYPE);
if (article.getID() == -1L)
{
LOG.log(Level.SEVERE, MessageFormat.format(Strings.error("0.title.1"),
MSG_NOT_IN_DB, article.getTitle()), new Exception("Dump"));
return false;
}
return true;
}
}