* landed support for reading topics and posting followup messages to a topic -

the basis of the conferencing engine is now firmly in place
* tweaks to the HTML Checker to make it better at breaking lines without
  leaving stranded punctuation at the beginning or end of a line
* also modified dictionary to better handle possessives and hyphenates
* as always, miscellaneous tweaks and bugfizes as I spot them
This commit is contained in:
Eric J. Bowersox
2001-02-07 21:12:38 +00:00
parent 8bcc80ddd7
commit 66b7fea53b
50 changed files with 3304 additions and 75 deletions
@@ -64,4 +64,9 @@ public interface TopicContext
public abstract void fixSeen() throws DataException;
public abstract List getMessages(int low, int high) throws DataException, AccessError;
public abstract TopicMessageContext postNewMessage(long parent, String pseud, String text)
throws DataException, AccessError;
} // end interface TopicContext
@@ -0,0 +1,64 @@
/*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (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.mozilla.org/MPL/>.
*
* Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
* WARRANTY OF ANY KIND, either express or implied. See the License for the specific
* language governing rights and limitations under the License.
*
* The Original Code is the Venice Web Community System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.core;
import java.util.Date;
public interface TopicMessageContext
{
public abstract long getPostID();
public abstract long getParentPostID();
public abstract int getPostNumber();
public abstract int getNumLines();
public abstract int getCreatorUID();
public abstract String getCreatorName() throws DataException;
public abstract Date getPostDate();
public abstract boolean isHidden();
public abstract boolean isScribbled();
public abstract boolean isNuked();
public abstract Date getScribbleDate();
public abstract String getPseud();
public abstract String getBodyText() throws DataException;
public abstract boolean hasAttachment();
public abstract boolean canHide();
public abstract boolean canScribble();
public abstract boolean canNuke();
public abstract void setHidden(boolean flag) throws DataException, AccessError;
public abstract void scribble() throws DataException, AccessError;
public abstract void nuke() throws DataException, AccessError;
} // end interface TopicMessageContext
@@ -17,8 +17,10 @@
*/
package com.silverwrist.venice.core.impl;
import java.sql.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;
import com.silverwrist.venice.core.DataException;
public interface ConferenceBackend extends SIGBackend
{
@@ -32,4 +34,14 @@ public interface ConferenceBackend extends SIGBackend
public abstract String realConfAlias();
public abstract boolean userCanScribble();
public abstract boolean userCanNuke();
public abstract boolean userCanRead();
public abstract boolean userCanPost();
public abstract void touchUpdate(Connection conn, java.util.Date date) throws DataException;
} // end interface ConferenceBackend
@@ -932,6 +932,37 @@ class ConferenceCoreData implements ConferenceData
} // end createNewTopic
public boolean canScribblePosts(int level)
{
return (level>=nuke_level);
} // end canScribblePosts
public boolean canNukePosts(int level)
{
return (level>=nuke_level);
} // end canNukePosts
public synchronized void touchUpdate(Connection conn, java.util.Date date) throws DataException
{
try
{ // update the last update date
Statement stmt = conn.createStatement();
StringBuffer sql = new StringBuffer("UPDATE confs SET lastupdate = '");
sql.append(SQLUtil.encodeDate(date)).append("' WHERE confid = ").append(confid).append(';');
stmt.executeUpdate(sql.toString());
last_update = date;
} // end try
catch (SQLException e)
{ // convert the SQLException
throw new DataException("Database error updating conference: " + e.getMessage(),e);
} // end catch
} // end touchUpdate
/*--------------------------------------------------------------------------------
* External static operations (usable only from within package)
*--------------------------------------------------------------------------------
@@ -17,6 +17,7 @@
*/
package com.silverwrist.venice.core.impl;
import java.sql.Connection;
import java.util.Date;
import java.util.List;
import com.silverwrist.venice.core.DataException;
@@ -79,4 +80,10 @@ public interface ConferenceData extends ReferencedData
public abstract ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body,
int body_lines) throws DataException;
public abstract boolean canScribblePosts(int level);
public abstract boolean canNukePosts(int level);
public abstract void touchUpdate(Connection conn, Date date) throws DataException;
} // end interface ConferenceData
@@ -17,6 +17,7 @@
*/
package com.silverwrist.venice.core.impl;
import java.sql.Connection;
import java.util.Date;
import java.util.List;
import com.silverwrist.venice.core.DataException;
@@ -91,4 +92,10 @@ public interface ConferenceSIGContext extends ReferencedData
public abstract ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body,
int body_lines) throws DataException;
public abstract boolean canScribblePosts(int level);
public abstract boolean canNukePosts(int level);
public abstract void touchUpdate(Connection conn, Date date) throws DataException;
} // end interface ConferenceSIGContext
@@ -618,4 +618,34 @@ class ConferenceSIGContextImpl implements ConferenceSIGContext
} // end createNewTopic
public boolean canScribblePosts(int level)
{
ConferenceData c = getConferenceDataNE();
if (c==null)
return false;
if (level<this.level)
return c.canScribblePosts(this.level);
else
return c.canScribblePosts(level);
} // end canScribblePosts
public boolean canNukePosts(int level)
{
ConferenceData c = getConferenceDataNE();
if (c==null)
return false;
if (level<this.level)
return c.canNukePosts(this.level);
else
return c.canNukePosts(level);
} // end canNukePosts
public void touchUpdate(Connection conn, java.util.Date date) throws DataException
{
getConferenceData().touchUpdate(conn,date);
} // end touchUpdate
} // end class ConferenceSIGContextImpl
@@ -1047,6 +1047,42 @@ class ConferenceUserContextImpl implements ConferenceContext, ConferenceBackend
} // end realConfAlias
public boolean userCanScribble()
{
ConferenceSIGContext c = getConferenceDataNE();
if (c==null)
return false;
return c.canScribblePosts(level);
} // end userCanScribble
public boolean userCanNuke()
{
ConferenceSIGContext c = getConferenceDataNE();
if (c==null)
return false;
return c.canNukePosts(level);
} // end userCanNuke
public boolean userCanRead()
{
return canReadConference();
} // end userCanRead
public boolean userCanPost()
{
return canPostToConference();
} // end userCanPost
public void touchUpdate(Connection conn, java.util.Date date) throws DataException
{
getConferenceData().touchUpdate(conn,date);
} // end getConferenceData
/*--------------------------------------------------------------------------------
* Static functions usable only from within the package
*--------------------------------------------------------------------------------
@@ -0,0 +1,706 @@
/*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (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.mozilla.org/MPL/>.
*
* Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
* WARRANTY OF ANY KIND, either express or implied. See the License for the specific
* language governing rights and limitations under the License.
*
* The Original Code is the Venice Web Community System.
*
* The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
* for Silverwrist Design Studios. Portions created by Eric J. Bowersox are
* Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved.
*
* Contributor(s):
*/
package com.silverwrist.venice.core.impl;
import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.venice.db.*;
import com.silverwrist.venice.security.AuditRecord;
import com.silverwrist.venice.core.*;
class TopicMessageUserContextImpl implements TopicMessageContext
{
/*--------------------------------------------------------------------------------
* Static data members
*--------------------------------------------------------------------------------
*/
private static Category logger = Category.getInstance(TopicMessageUserContextImpl.class.getName());
/*--------------------------------------------------------------------------------
* Attributes
*--------------------------------------------------------------------------------
*/
private EngineBackend engine;
private ConferenceBackend conf;
private DataPool datapool;
private long postid;
private long parent;
private int num;
private int linecount;
private int creator_uid;
private java.util.Date posted;
private boolean hidden;
private int scribble_uid;
private java.util.Date scribble_date;
private String pseud;
private int datalen;
private String filename;
private String mimetype;
private boolean nuked = false;
private String creator_cache = null;
private String text_cache = null;
/*--------------------------------------------------------------------------------
* Constructors
*--------------------------------------------------------------------------------
*/
protected TopicMessageUserContextImpl(EngineBackend engine, ConferenceBackend conf, DataPool datapool,
long postid, long parent, int num, int linecount, int creator_uid,
java.util.Date posted, boolean hidden, int scribble_uid,
java.util.Date scribble_date, String pseud, int datalen,
String filename, String mimetype)
{
this.engine = engine;
this.conf = conf;
this.datapool = datapool;
this.postid = postid;
this.parent = parent;
this.num = num;
this.linecount = linecount;
this.creator_uid = creator_uid;
this.posted = posted;
this.hidden = hidden;
this.scribble_uid = scribble_uid;
this.scribble_date = scribble_date;
this.pseud = pseud;
this.datalen = datalen;
this.filename = filename;
this.mimetype = mimetype;
} // end constructor
TopicMessageUserContextImpl(EngineBackend engine, ConferenceBackend conf, DataPool datapool,
long postid, long parent, int num, int linecount, int creator_uid,
java.util.Date posted, String pseud)
{
this.engine = engine;
this.conf = conf;
this.datapool = datapool;
this.postid = postid;
this.parent = parent;
this.num = num;
this.linecount = linecount;
this.creator_uid = creator_uid;
this.posted = posted;
this.hidden = false;
this.scribble_uid = 0;
this.scribble_date = null;
this.pseud = pseud;
this.datalen = 0;
this.filename = null;
this.mimetype = null;
} // end constructor
/*--------------------------------------------------------------------------------
* Internal functions
*--------------------------------------------------------------------------------
*/
private static String quickGetUserName(Connection conn, int uid) throws SQLException
{
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT username FROM users WHERE uid = " + String.valueOf(uid) + ";");
if (rs.next())
return rs.getString(1);
else
return "(unknown)";
} // end quickGetUserName
private void refresh(Connection conn) throws SQLException
{
Statement stmt = conn.createStatement();
StringBuffer sql = new StringBuffer("SELECT p.hidden, p.scribble_uid, p.scribble_date, p.pseud, "
+ "a.datalen, a.filename, a.mimetype FROM posts p LEFT JOIN "
+ "postattach a ON p.postid = a.postid WHERE p.postid = ");
sql.append(postid).append(';');
ResultSet rs = stmt.executeQuery(sql.toString());
if (rs.next())
{ // update a variety of fields
hidden = rs.getBoolean(1);
scribble_uid = rs.getInt(2);
scribble_date = SQLUtil.getFullDateTime(rs,3);
pseud = rs.getString(4);
datalen = rs.getInt(5);
filename = rs.getString(6);
mimetype = rs.getString(7);
} // end if
else
{ // the post has been nuked - update accordingly
linecount = 0;
creator_uid = -1;
posted = null;
hidden = false;
scribble_uid = -1;
scribble_date = null;
pseud = null;
datalen = 0;
filename = null;
mimetype = null;
nuked = true;
creator_cache = null;
text_cache = null;
} // end else
} // end refresh
/*--------------------------------------------------------------------------------
* Implementations from interface TopicMessageContext
*--------------------------------------------------------------------------------
*/
public long getPostID()
{
return postid;
} // end getPostID
public long getParentPostID()
{
return parent;
} // end getParentPostID
public int getPostNumber()
{
return num;
} // end getPostNumber
public int getNumLines()
{
return linecount;
} // end getNumLines
public int getCreatorUID()
{
return creator_uid;
} // end getCreatorUID
public String getCreatorName() throws DataException
{
if (creator_cache==null)
{ // we don't have the user name yet, get it out of the database
if (nuked)
return null; // post nuked!
Connection conn = null;
try
{ // use a database connection to get the user name
conn = datapool.getConnection();
refresh(conn);
if (nuked)
return null; // post nuked!
creator_cache = quickGetUserName(conn,creator_uid);
} // end try
catch (SQLException e)
{ // turn this into a DataException
logger.error("DB error reading user name: " + e.getMessage(),e);
throw new DataException("unable to retrieve user name: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
} // end if
return creator_cache;
} // end getCreatorName
public java.util.Date getPostDate()
{
return posted;
} // end getPostDate
public boolean isHidden()
{
return hidden;
} // end isHidden
public boolean isScribbled()
{
return (scribble_date!=null);
} // end isScribbled
public boolean isNuked()
{
return nuked;
} // end isNuked
public java.util.Date getScribbleDate()
{
return scribble_date;
} // end getScribbleDate
public String getPseud()
{
return pseud;
} // return pseud
public String getBodyText() throws DataException
{
if (text_cache==null)
{ // we don't have the body text yet, go get it
Connection conn = null;
if (nuked)
return null; // post nuked!
try
{ // use a database connection to get the body text
conn = datapool.getConnection();
refresh(conn);
if (nuked)
return null; // post nuked!
if (scribble_date==null)
{ // let's go get the body text!
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT data FROM postdata WHERE postid = "
+ String.valueOf(postid) + ";");
if (rs.next())
text_cache = rs.getString(1);
else
return "Data Missing"; // FUTURE: throw an exception?
} // end if
else // for scribbled posts, we return the scribbler's name only
text_cache = quickGetUserName(conn,scribble_uid);
} // end try
catch (SQLException e)
{ // turn this into a DataException
logger.error("DB error reading post data: " + e.getMessage(),e);
throw new DataException("unable to retrieve post data: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
} // end if
return text_cache;
} // end getBodyText
public boolean hasAttachment()
{
return (mimetype!=null);
} // end hasAttachment
public boolean canHide()
{
return ((creator_uid==conf.realUID()) || conf.userCanHide());
} // end canHide
public boolean canScribble()
{
return ((creator_uid==conf.realUID()) || conf.userCanScribble());
} // end canScribble
public boolean canNuke()
{
return conf.userCanNuke();
} // end canNuke
public void setHidden(boolean flag) throws DataException, AccessError
{
if ((creator_uid!=conf.realUID()) && !(conf.userCanHide()))
{ // we can't change the hidden status!
logger.error("trying to set hidden status of post w/o permission!");
throw new AccessError("You are not permitted to change the hidden status of this message.");
} // end if
if (nuked || (scribble_date!=null))
return; // changing the status of a nuked or scribbled post is futile
Connection conn = null;
AuditRecord ar = null;
try
{ // open up a database connection
conn = datapool.getConnection();
Statement stmt = conn.createStatement();
// lock the tables we reference
stmt.executeUpdate("LOCK TABLES posts WRITE, postattach READ;");
try
{ // first, make sure we have the right status for our post
refresh(conn);
if (nuked || (scribble_date!=null))
return; // changing the status of a nuked or scribbled post is futile
if (hidden==flag)
return; // this is a no-op
// update the "hidden" flag in the database
StringBuffer sql = new StringBuffer("UPDATE posts SET hidden = ");
sql.append(flag ? '1' : '0').append(" WHERE postid = ").append(postid).append(';');
stmt.executeUpdate(sql.toString());
hidden = flag; // store flag
} // end try
finally
{ // unlock the tables before we go
Statement ulk_stmt = conn.createStatement();
ulk_stmt.executeUpdate("UNLOCK TABLES;");
} // end finally
// record what we did in an audit record
ar = new AuditRecord(AuditRecord.HIDE_MESSAGE,conf.realUID(),conf.userRemoteAddress(),
conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",post="
+ String.valueOf(postid),flag ? "hide" : "unhide");
} // end try
catch (SQLException e)
{ // turn this into a DataException
logger.error("DB error setting hidden status: " + e.getMessage(),e);
throw new DataException("unable to set hidden status: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
try
{ // save off the audit record before we go, though
if ((ar!=null) && (conn!=null))
ar.store(conn);
} // end try
catch (SQLException e)
{ // we couldn't store the audit record!
logger.error("DB error saving audit record: " + e.getMessage(),e);
} // end catch
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
} // end setHidden
public void scribble() throws DataException, AccessError
{
if ((creator_uid!=conf.realUID()) && !(conf.userCanScribble()))
{ // we can't scribble this post
logger.error("trying to scribble post w/o permission!");
throw new AccessError("You are not permitted to scribble this message.");
} // end if
if (nuked || (scribble_date!=null))
return; // scribbling a nuked or scribbled post is futile
Connection conn = null;
AuditRecord ar = null;
try
{ // open up a database connection
conn = datapool.getConnection();
Statement stmt = conn.createStatement();
// lock the tables we reference
stmt.executeUpdate("LOCK TABLES posts WRITE, postdata WRITE, postattach WRITE;");
try
{ // first, make sure we have the right status for our post
refresh(conn);
if (nuked || (scribble_date!=null))
return; // scribbling a nuked or scribbled post is futile
// First, set the appropriate "scribbled" information in the "header".
StringBuffer sql = new StringBuffer("UPDATE posts SET linecount = 0, hidden = 0, scribble_uid = ");
sql.append(conf.realUID()).append(", scribble_date = '");
java.util.Date now = new java.util.Date();
final String scribble_pseud = "<EM><B>(Scribbled)</B></EM>"; // TODO: configurable option
sql.append(SQLUtil.encodeDate(now)).append("', pseud = '").append(scribble_pseud);
sql.append("' WHERE postid = ").append(postid).append(';');
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
stmt.executeUpdate(sql.toString());
// Determine if we need to "rub out" the post before we delete it.
ResultSet rs = stmt.executeQuery("SELECT LENGTH(data) FROM postdata WHERE postid = "
+ String.valueOf(postid) + ";");
if (rs.next())
{ // use this data to overwrite the post with X's
int len = rs.getInt(1);
if (len>0)
{ // construct the "rubout" statement and execute it
sql.setLength(0);
sql.append("UPDATE postdata SET data = '");
while (len>0)
{ // generate a string of X's the length of the post
sql.append('X');
len--;
} // end while
sql.append("' WHERE postid = ").append(postid).append(';');
stmt.executeUpdate(sql.toString());
} // end if
// else not much need to do a rubout
} // end if
// else don't try...we're deleting the row anyway
// Delete the actual post data row.
sql.setLength(0);
sql.append("DELETE FROM postdata WHERE postid = ").append(postid).append(';');
stmt.executeUpdate(sql.toString());
// Delete the attachment data row.
// FUTURE: can we do an overwrite on the attachment the way we did on the post data?
sql.setLength(0);
sql.append("DELETE FROM postattach WHERE postid = ").append(postid).append(';');
stmt.executeUpdate(sql.toString());
// Update our internal data fields.
linecount = 0;
hidden = false;
scribble_uid = conf.realUID();
scribble_date = now;
pseud = scribble_pseud;
text_cache = null;
} // end try
finally
{ // unlock the tables before we go
Statement ulk_stmt = conn.createStatement();
ulk_stmt.executeUpdate("UNLOCK TABLES;");
} // end finally
// record what we did in an audit record
ar = new AuditRecord(AuditRecord.SCRIBBLE_MESSAGE,conf.realUID(),conf.userRemoteAddress(),
conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",post="
+ String.valueOf(postid));
} // end try
catch (SQLException e)
{ // turn this into a DataException
logger.error("DB error scribbling post: " + e.getMessage(),e);
throw new DataException("unable to scribble message: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
try
{ // save off the audit record before we go, though
if ((ar!=null) && (conn!=null))
ar.store(conn);
} // end try
catch (SQLException e)
{ // we couldn't store the audit record!
logger.error("DB error saving audit record: " + e.getMessage(),e);
} // end catch
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
} // end scribble
public void nuke() throws DataException, AccessError
{
if (!(conf.userCanNuke()))
{ // we can't scribble this post
logger.error("trying to nuke post w/o permission!");
throw new AccessError("You are not permitted to nuke this message.");
} // end if
if (nuked)
return; // nuking a nuked post is futile
Connection conn = null;
AuditRecord ar = null;
try
{ // open up a database connection
conn = datapool.getConnection();
Statement stmt = conn.createStatement();
// lock the tables we reference
stmt.executeUpdate("LOCK TABLES posts WRITE, postdata WRITE, postattach WRITE, postdogear WRITE;");
try
{ // first, make sure we have the right status for our post
refresh(conn);
if (nuked)
return; // nuking a nuked post is futile
// Delete any and all references to this post!
stmt.executeUpdate("DELETE FROM posts WHERE postid = " + String.valueOf(postid) + ";");
stmt.executeUpdate("DELETE FROM postdata WHERE postid = " + String.valueOf(postid) + ";");
stmt.executeUpdate("DELETE FROM postattach WHERE postid = " + String.valueOf(postid) + ";");
stmt.executeUpdate("DELETE FROM postdogear WHERE postid = " + String.valueOf(postid) + ";");
// Update our internal variables.
linecount = 0;
creator_uid = -1;
posted = null;
hidden = false;
scribble_uid = -1;
scribble_date = null;
pseud = null;
datalen = 0;
filename = null;
mimetype = null;
nuked = true;
creator_cache = null;
text_cache = null;
} // end try
finally
{ // unlock the tables before we go
Statement ulk_stmt = conn.createStatement();
ulk_stmt.executeUpdate("UNLOCK TABLES;");
} // end finally
// record what we did in an audit record
ar = new AuditRecord(AuditRecord.NUKE_MESSAGE,conf.realUID(),conf.userRemoteAddress(),
conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",post="
+ String.valueOf(postid));
} // end try
catch (SQLException e)
{ // turn this into a DataException
logger.error("DB error nuking post: " + e.getMessage(),e);
throw new DataException("unable to nuke message: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
try
{ // save off the audit record before we go, though
if ((ar!=null) && (conn!=null))
ar.store(conn);
} // end try
catch (SQLException e)
{ // we couldn't store the audit record!
logger.error("DB error saving audit record: " + e.getMessage(),e);
} // end catch
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
} // end nuke
/*--------------------------------------------------------------------------------
* External static operations
*--------------------------------------------------------------------------------
*/
static List loadMessageRange(EngineBackend engine, ConferenceBackend conf, DataPool datapool, int topicid,
int post_low, int post_high) throws DataException
{
if (logger.isDebugEnabled())
logger.debug("loadMessageRange for conf # " + String.valueOf(conf.realConfID()) + ", topic #"
+ String.valueOf(topicid) + ", range [" + String.valueOf(post_low) + ", "
+ String.valueOf(post_high) + "]");
Vector rc = new Vector();
Connection conn = null; // pooled database connection
try
{ // get a database connection
conn = datapool.getConnection();
Statement stmt = conn.createStatement();
// run a query to get all the posts in a particular topic
StringBuffer sql =
new StringBuffer("SELECT p.postid, p.parent, p.num, p.linecount, p.creator_uid, p.posted, "
+ "p.hidden, p.scribble_uid, p.scribble_date, p.pseud, a.datalen, a.filename, "
+ "a.mimetype FROM posts p LEFT JOIN postattach a ON p.postid = a.postid "
+ "WHERE p.topicid = ");
sql.append(topicid).append(" AND p.num >= ").append(post_low).append(" AND p.num <= ");
sql.append(post_high).append(" ORDER BY p.num ASC;");
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
ResultSet rs = stmt.executeQuery(sql.toString());
while (rs.next())
{ // create implementation objects and shove them into the return vector
TopicMessageContext val =
new TopicMessageUserContextImpl(engine,conf,datapool,rs.getLong(1),rs.getLong(2),rs.getInt(3),
rs.getInt(4),rs.getInt(5),SQLUtil.getFullDateTime(rs,6),
rs.getBoolean(7),rs.getInt(8),SQLUtil.getFullDateTime(rs,9),
rs.getString(10),rs.getInt(11),rs.getString(12),rs.getString(13));
rc.add(val);
} // end while
} // end try
catch (SQLException e)
{ // turn SQLException into data exception
logger.error("DB error reading message entries: " + e.getMessage(),e);
throw new DataException("unable to retrieve messages: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
return new ReadOnlyVector(rc); // wrap the return vector
} // end loadMessageRange
} // end class TopicMessageUserContextImpl
@@ -21,6 +21,7 @@ import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.venice.db.*;
import com.silverwrist.venice.htmlcheck.*;
import com.silverwrist.venice.security.AuditRecord;
import com.silverwrist.venice.core.*;
@@ -109,11 +110,13 @@ class TopicUserContextImpl implements TopicContext
private static ResultSet queryByTopic(Statement stmt, int topicid, int uid) throws SQLException
{
StringBuffer sql =
new StringBuffer("SELECT t.topicid, t.num, t.creator_uid, t.top_message, t.frozen, t.archived, "
+ "t.createdate, t.lastupdate, t.name, IFNULL(s.hidden,0) AS hidden, "
+ "(t.top_message - IFNULL(s.last_message,-1)) AS unread FROM topics t "
+ "LEFT JOIN topicsettings s ON t.topicid = s.topicid AND s.uid = ");
sql.append(uid).append(" WHERE t.topicid = ").append(topicid).append(';');
new StringBuffer("SELECT topics.topicid, topics.num, topics.creator_uid, topics.top_message, "
+ "topics.frozen, topics.archived, topics.createdate, topics.lastupdate, "
+ "topics.name, IFNULL(topicsettings.hidden,0) AS hidden, "
+ "(topics.top_message - IFNULL(topicsettings.last_message,-1)) AS unread "
+ "FROM topics LEFT JOIN topicsettings ON topics.topicid = topicsettings.topicid "
+ "AND topicsettings.uid = ");
sql.append(uid).append(" WHERE topics.topicid = ").append(topicid).append(';');
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
return stmt.executeQuery(sql.toString());
@@ -134,8 +137,29 @@ class TopicUserContextImpl implements TopicContext
} // end if
private void refresh(Connection conn) throws SQLException
{
Statement stmt = conn.createStatement();
// perform a requery of the database
ResultSet rs = queryByTopic(stmt,topicid,conf.realUID());
if (rs.next())
{ // update the fields that are capable of changing
top_message = rs.getInt(4);
frozen = rs.getBoolean(5);
archived = rs.getBoolean(6);
lastupdate = SQLUtil.getFullDateTime(rs,8);
hidden = rs.getBoolean(10);
unread = rs.getInt(11);
} // end if
else // this topic must have been deleted - fsck it
makeDeleted();
} // end refresh
/*--------------------------------------------------------------------------------
* Implementatuions from interface TopicContext
* Implementations from interface TopicContext
*--------------------------------------------------------------------------------
*/
@@ -148,22 +172,7 @@ class TopicUserContextImpl implements TopicContext
try
{ // get a database connection
conn = datapool.getConnection();
Statement stmt = conn.createStatement();
// perform a requery of the database
ResultSet rs = queryByTopic(stmt,topicid,conf.realUID());
if (rs.next())
{ // update the fields that are capable of changing
top_message = rs.getInt(4);
frozen = rs.getBoolean(5);
archived = rs.getBoolean(6);
lastupdate = SQLUtil.getFullDateTime(rs,8);
hidden = rs.getBoolean(10);
unread = rs.getInt(11);
} // end if
else // this topic must have been deleted - fsck it
makeDeleted();
refresh(conn);
} // end try
catch (SQLException e)
@@ -524,8 +533,8 @@ class TopicUserContextImpl implements TopicContext
} // end try
catch (SQLException e)
{ // turn SQLException into data exception
logger.error("DB error setting topic data: " + e.getMessage(),e);
throw new DataException("unable to set topic hidden status: " + e.getMessage(),e);
logger.error("DB error setting topic user data: " + e.getMessage(),e);
throw new DataException("unable to set unread messages: " + e.getMessage(),e);
} // end catch
finally
@@ -543,6 +552,213 @@ class TopicUserContextImpl implements TopicContext
} // end fixSeen
public List getMessages(int low, int high) throws DataException, AccessError
{
if (!(conf.userCanRead()))
{ // they can't read messages in this topic!
logger.error("trying to read postings w/o permission!");
throw new AccessError("You do not have permission to read messages in this conference.");
} // end if
// reorder parameters so they come in the correct order!
if (low<=high)
return TopicMessageUserContextImpl.loadMessageRange(engine,conf,datapool,topicid,low,high);
else
return TopicMessageUserContextImpl.loadMessageRange(engine,conf,datapool,topicid,high,low);
} // end getMessages
public TopicMessageContext postNewMessage(long parent, String pseud, String text)
throws DataException, AccessError
{
if (!(conf.userCanPost()))
{ // they can't post in this topic!
logger.error("trying to post w/o permission!");
throw new AccessError("You do not have permission to post messages in this conference.");
} // end if
if (deleted)
{ // the topic has been deleted
logger.error("can't post to a deleted topic!");
throw new AccessError("You cannot post to a topic that has been deleted.");
} // end if
if (frozen && !(conf.userCanHide()))
{ // can't post to a frozen topic!
logger.error("can't post to a frozen topic!");
throw new AccessError("The topic is frozen, and you do not have permission to post to it.");
} // end if
if (archived && !(conf.userCanHide()))
{ // can't post to a frozen topic!
logger.error("can't post to an archived topic!");
throw new AccessError("The topic is archived, and you do not have permission to post to it.");
} // end if
// preprocess the two arguments through HTML checkers
HTMLChecker pseud_ch = engine.createCheckerObject(engine.HTMLC_POST_PSEUD);
HTMLChecker text_ch = engine.createCheckerObject(engine.HTMLC_POST_BODY);
try
{ // run both arguments through the HTML checker
pseud_ch.append(pseud);
pseud_ch.finish();
text_ch.append(text);
text_ch.finish();
} // end try
catch (AlreadyFinishedException e)
{ // this isn't right...
throw new InternalStateError("HTMLChecker erroneously throwing AlreadyFinishedException",e);
} // end catch
String real_pseud, real_text;
int text_linecount;
try
{ // retrieve the processed values
real_pseud = pseud_ch.getValue();
real_text = text_ch.getValue();
text_linecount = text_ch.getLines();
} // end try
catch (NotYetFinishedException e)
{ // this isn't right either!
throw new InternalStateError("HTMLChecker erroneously throwing NotYetFinishedException",e);
} // end catch
int new_post_num;
long new_post_id;
java.util.Date posted_date;
Connection conn = null;
AuditRecord ar = null;
try
{ // get a database connection
conn = datapool.getConnection();
Statement stmt = conn.createStatement();
// slap a lock on all the tables we need to touch
stmt.executeUpdate("LOCK TABLES confs WRITE, topics WRITE, posts WRITE, postdata WRITE, "
+ "confsettings WRITE, topicsettings READ;");
try
{ // refresh our current status and recheck allowed status
refresh(conn);
if (deleted)
{ // the topic has been deleted
logger.error("can't post to a deleted topic!");
throw new AccessError("You cannot post to a topic that has been deleted.");
} // end if
if (frozen && !(conf.userCanHide()))
{ // can't post to a frozen topic!
logger.error("can't post to a frozen topic!");
throw new AccessError("The topic is frozen, and you do not have permission to post to it.");
} // end if
if (archived && !(conf.userCanHide()))
{ // can't post to a frozen topic!
logger.error("can't post to an archived topic!");
throw new AccessError("The topic is archived, and you do not have permission to post to it.");
} // end if
// Determine what the new post number is.
new_post_num = top_message + 1;
// Add the post "header" to the posts table.
StringBuffer sql = new StringBuffer("INSERT INTO posts (parent, topicid, num, linecount, creator_uid, "
+ "posted, pseud) VALUES (");
sql.append(parent).append(", ").append(topicid).append(", ").append(new_post_num).append(", ");
sql.append(text_linecount).append(", ").append(conf.realUID()).append(", '");
posted_date = new java.util.Date();
sql.append(SQLUtil.encodeDate(posted_date)).append("', '").append(real_pseud).append("');");
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
stmt.executeUpdate(sql.toString());
// Retrieve the new post ID.
ResultSet rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
if (!(rs.next()))
throw new InternalStateError("postMessage(): Unable to get new post ID!");
new_post_id = rs.getLong(1);
// Touch the topic values to reflect the added post.
sql.setLength(0);
sql.append("UPDATE topics SET top_message = ").append(new_post_num).append(", lastupdate = '");
sql.append(SQLUtil.encodeDate(posted_date)).append("' WHERE topicid = ").append(topicid).append(';');
if (logger.isDebugEnabled())
logger.debug("SQL: " + sql.toString());
stmt.executeUpdate(sql.toString());
// insert the post data
sql.setLength(0);
sql.append("INSERT INTO postdata (postid, data) VALUES (").append(new_post_id).append(", '");
sql.append(real_text).append("');");
stmt.executeUpdate(sql.toString());
// mark that we posted to the conference
conf.touchUpdate(conn,posted_date);
conf.touchPost(conn,posted_date);
// fill in our own local variables to reflect the update
top_message = new_post_num;
lastupdate = posted_date;
} // end try
finally
{ // make sure we unlock the tables when we're done
Statement ulk_stmt = conn.createStatement();
ulk_stmt.executeUpdate("UNLOCK TABLES;");
} // end finally
// record what we did in an audit record
ar = new AuditRecord(AuditRecord.POST_MESSAGE,conf.realUID(),conf.userRemoteAddress(),
conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",topic="
+ String.valueOf(topicid) + ",post=" + String.valueOf(new_post_id),
"pseud=" + real_pseud);
} // end try
catch (SQLException e)
{ // turn SQLException into data exception
logger.error("DB error posting message: " + e.getMessage(),e);
throw new DataException("unable to post message: " + e.getMessage(),e);
} // end catch
finally
{ // make sure we release the connection before we go
try
{ // save off the audit record before we go, though
if ((ar!=null) && (conn!=null))
ar.store(conn);
} // end try
catch (SQLException e)
{ // we couldn't store the audit record!
logger.error("DB error saving audit record: " + e.getMessage(),e);
} // end catch
if (conn!=null)
datapool.releaseConnection(conn);
} // end finally
// return the new message context
return new TopicMessageUserContextImpl(engine,conf,datapool,new_post_id,parent,new_post_num,
text_linecount,conf.realUID(),posted_date,real_pseud);
} // end postMessage
/*--------------------------------------------------------------------------------
* External operations usable only from within the package
*--------------------------------------------------------------------------------