/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev$
* Last modified by $Author$
* $Date$
*/
package tigase.server.sreceiver;
//~--- non-JDK imports --------------------------------------------------------
import tigase.server.Message;
import tigase.server.Packet;
import tigase.xml.XMLUtils;
import tigase.xmpp.StanzaType;
import static tigase.server.sreceiver.PropertyConstants.*;
//~--- JDK imports ------------------------------------------------------------
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
//~--- classes ----------------------------------------------------------------
/**
* This subscription tasks allows you to publish short news on your site.
* In fact this task behaves exactly as <code>NewsDistributor</code> tasks
* but it also writes all messages content to a table in database.
* Web application can then read content from this table and publish it
* on the Web site.
*
* Format of the table is as follows (schema definition in MySQL script):
* <pre>
* create table short_news (
* -- Automatic record ID
* snid bigint unsigned NOT NULL auto_increment,
* -- Automaticly generated timestamp and automaticly updated on change
* publishing_time timestamp,
* -- Optional news type: 'shorts', 'minis', 'techs', 'funs'....
* news_type varchar(10),
* -- Author JID
* author varchar(128) NOT NULL,
* -- Short subject - this is short news, right?
* subject varchar(128) NOT NULL,
* -- Short news message - this is short news, right?
* body varchar(1024) NOT NULL,
* primary key(snid),
* key publishing_date (publishing_date),
* key author (author)
* ) default character set utf8;
* </pre>
*
* Created: Sat May 26 10:25:42 2007
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class ShortNewsPublisher extends RepoRosterTask {
private static final String DB_CONNECTION_DISPL_NAME = "Database connection string";
private static final String DB_CONNECTION_PROP_KEY = "db-connection-string";
private static final String DB_CONNECTION_PROP_VAL =
"jdbc:mysql://localhost/tigase?user=root&password=mypass";
private static final String DB_TABLE_DISPL_NAME = "Database table name";
private static final String DB_TABLE_PROP_KEY = "db-table";
private static final String DB_TABLE_PROP_VAL = "short_news";
private static final String NEWS_TYPE_DISPL_NAME = "News type";
private static final String NEWS_TYPE_PROP_KEY = "news-type";
private static final String NEWS_TYPE_PROP_VAL = "minis";
private static final String TASK_HELP =
"This tasks writes all messages to special table in database"
+ " called 'short_news' and notifies all subscribed users about"
+ " new post. Table in database keeps following information about"
+ " post: publishing_time, author, subject, body. This subscription"
+ " task is ideal for publish short news on your Web site."
+ " Users can subscribe to the news just by adding task JID"
+ " to their roster, unsubscribing is equally simple - remove"
+ " JID from roster to stop receiving news. By default subscription"
+ " to this task is moderated."
;
private static final String TASK_TYPE = "Short news publisher";
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger.getLogger("tigase.server.ssender.JDBCTask");
//~--- constant enums -------------------------------------------------------
private enum command { help, delete, update; }
//~--- fields ---------------------------------------------------------------
/**
* <code>conn</code> variable keeps connection to database.
*/
private Connection conn = null;
/**
* <code>conn_valid_st</code> is a prepared statement keeping query used
* to validate connection to database:
* <pre>select 1</pre>
*/
private PreparedStatement conn_valid_st = null;
/**
* <code>connectionValidateInterval</code> is kind of constant keeping minimum
* interval for validating connection to database.
*/
private long connectionValidateInterval = 1000 * 60;
/**
* <code>db_conn</code> field stores database connection string.
*/
private String db_conn = null;
/**
* <code>delete_post</code> is a prepared statement for deleting post
* from database.
*/
private PreparedStatement delete_post = null;
/**
* <code>insert_post</code> is a prepared statement for inserting new
* post to database.
*/
private PreparedStatement insert_post = null;
/**
* <code>lastConnectionValidated</code> variable keeps time where the
* connection was validated for the last time.
*/
private long lastConnectionValidated = 0;
/**
* <code>newsType</code> is a type of news saved to database.
*/
private String newsType = "minis";
/**
* <code>tableName</code> keeps a table name where are stanza packets waiting
* for sending. Default is <code>xmpp_stanza</code>
*/
private String tableName = "short_news";
/**
* <code>update_post</code> is a prepared statement for updating existing
* post in database.
*/
private PreparedStatement update_post = null;
;
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param results
*/
public void destroy(Queue<Packet> results) {
super.destroy(results);
try {
conn_valid_st.close();
insert_post.close();
conn.close();
} catch (Exception e) {
// Ignore.
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Map<String, PropertyItem> getDefaultParams() {
Map<String, PropertyItem> defs = super.getDefaultParams();
defs.put(MESSAGE_TYPE_PROP_KEY,
new PropertyItem(MESSAGE_TYPE_PROP_KEY,
MESSAGE_TYPE_DISPL_NAME,
MessageType.NORMAL));
defs.put(SUBSCR_RESTRICTIONS_PROP_KEY,
new PropertyItem(SUBSCR_RESTRICTIONS_PROP_KEY,
SUBSCR_RESTRICTIONS_DISPL_NAME,
SubscrRestrictions.MODERATED));
defs.put(DESCRIPTION_PROP_KEY,
new PropertyItem(DESCRIPTION_PROP_KEY,
DESCRIPTION_DISPL_NAME,
"Short news for the Web site..."));
defs.put(DB_CONNECTION_PROP_KEY,
new PropertyItem(DB_CONNECTION_PROP_KEY,
DB_CONNECTION_DISPL_NAME,
DB_CONNECTION_PROP_VAL));
defs.put(DB_TABLE_PROP_KEY,
new PropertyItem(DB_TABLE_PROP_KEY, DB_TABLE_DISPL_NAME, DB_TABLE_PROP_VAL));
defs.put(NEWS_TYPE_PROP_KEY,
new PropertyItem(NEWS_TYPE_PROP_KEY,
NEWS_TYPE_DISPL_NAME,
NEWS_TYPE_PROP_VAL));
return defs;
}
/**
* Method description
*
*
* @return
*/
public String getHelp() {
return TASK_HELP;
}
/**
* Method description
*
*
* @return
*/
public String getType() {
return TASK_TYPE;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param map
*/
public void setParams(final Map<String, Object> map) {
super.setParams(map);
Map<String, PropertyItem> props = getParams();
if (map.get(DB_TABLE_PROP_KEY) != null) {
tableName = (String) map.get(DB_TABLE_PROP_KEY);
props.put(DB_TABLE_PROP_KEY,
new PropertyItem(DB_TABLE_PROP_KEY, DB_TABLE_DISPL_NAME, tableName));
}
if (map.get(NEWS_TYPE_PROP_KEY) != null) {
newsType = (String) map.get(NEWS_TYPE_PROP_KEY);
props.put(NEWS_TYPE_PROP_KEY,
new PropertyItem(NEWS_TYPE_PROP_KEY, NEWS_TYPE_DISPL_NAME, newsType));
}
if (map.get(DB_CONNECTION_PROP_KEY) != null) {
db_conn = (String) map.get(DB_CONNECTION_PROP_KEY);
props.put(DB_CONNECTION_PROP_KEY,
new PropertyItem(DB_CONNECTION_PROP_KEY,
DB_CONNECTION_DISPL_NAME,
db_conn));
try {
initRepo();
} catch (SQLException e) {
log.log(Level.SEVERE, "Problem initializing database connection.", e);
}
}
}
//~--- methods --------------------------------------------------------------
@Override
protected void processMessage(Packet packet, Queue<Packet> results) {
if (isPostCommand(packet)) {
runCommand(packet, results);
} else {
super.processMessage(packet, results);
addPost(packet, results);
}
}
private void addPost(Packet packet, Queue<Packet> results) {
try {
checkConnection();
String author = packet.getStanzaFrom().getBareJID().toString();
String subject = packet.getElemCData("/message/subject");
String body = packet.getElemCData("/message/body");
if (body != null) {
if ((subject == null) || (subject.length() == 0)) {
int dotIdx = body.indexOf('.');
if (dotIdx > 0) {
subject = body.substring(0, dotIdx);
} else {
subject = "--";
}
}
insert_post.setString(1, author);
insert_post.setString(2, XMLUtils.unescape(subject));
insert_post.setString(3, XMLUtils.unescape(body));
insert_post.executeUpdate();
results.offer(Message.getMessage(packet.getStanzaTo(), packet.getStanzaFrom(),
StanzaType.chat,
"Your post has been successfuly submitted.",
"Short news submitions result.", null,
packet.getStanzaId()));
} else {
// if body is null it might be an empty message used for
// announcing other side that the user has just started typing
// message, such messages we just ignore
results.offer(Message.getMessage(packet.getStanzaTo(), packet.getStanzaFrom(),
StanzaType.normal,
"Missing body, post has NOT been submitted.",
"Short news submitions result.", null,
packet.getStanzaId()));
}
} catch (SQLException e) {
log.log(Level.SEVERE, "Problem inserting new post: " + packet.toString(), e);
results.offer(Message.getMessage(packet.getStanzaTo(), packet.getStanzaFrom(),
StanzaType.normal,
"There was a problem with post submitting: " + e,
"Short news submitions result.", null,
packet.getStanzaId()));
}
}
/**
* <code>checkConnection</code> method checks whether connection to database
* is still valid, if not it simply reconnect and reinitializes database
* backend.
*
* @return a <code>boolean</code> value
* @exception SQLException if an error occurs
*/
private boolean checkConnection() throws SQLException {
try {
long tmp = System.currentTimeMillis();
if ((tmp - lastConnectionValidated) >= connectionValidateInterval) {
conn_valid_st.executeQuery();
lastConnectionValidated = tmp;
} // end of if ()
} catch (Exception e) {
initRepo();
} // end of try-catch
return true;
}
private String commandsHelp() {
return "Available commands are:\n" + "//help - display this help info\n"
+ "//update N - update post number N, posts content to update\n"
+ " starts from the next line\n"
+ "//delete N - remove post number N";
}
private void deletePost(long snid) throws SQLException {
checkConnection();
delete_post.setLong(1, snid);
delete_post.executeUpdate();
}
/**
* <code>initRepo</code> method initializes database backend - connects to
* database, creates prepared statements and sets basic variables.
*
* @exception SQLException if an error occurs
*/
private void initRepo() throws SQLException {
conn = DriverManager.getConnection(db_conn);
conn.setAutoCommit(true);
String query = "select 1;";
conn_valid_st = conn.prepareStatement(query);
if ((newsType == null) || (newsType.length() == 0)) {
query = "insert into " + tableName + " (news_type, author, subject, body) "
+ " values (null, ?, ?, ?)";
} else {
query = "insert into " + tableName + " (news_type, author, subject, body) "
+ " values ('" + newsType + "', ?, ?, ?)";
}
insert_post = conn.prepareStatement(query);
query = "delete from " + tableName + " where snid = ?";
delete_post = conn.prepareStatement(query);
query = "update " + tableName + " set subject = ?, body = ?" + " where snid = ? ";
update_post = conn.prepareStatement(query);
}
//~--- get methods ----------------------------------------------------------
private boolean isPostCommand(Packet packet) {
String body = packet.getElemCData("/message/body");
if (body != null) {
for (command comm : command.values()) {
if (body.startsWith("//" + comm.toString())) {
return true;
}
}
}
return false;
}
//~--- methods --------------------------------------------------------------
private void runCommand(Packet packet, Queue<Packet> results) {
String body = packet.getElemCData("/message/body");
String[] body_split = body.split(" |\n|\r");
try {
command comm = command.valueOf(body_split[0].substring(2));
switch (comm) {
case help :
results.offer(Message.getMessage(packet.getStanzaTo(), packet.getStanzaFrom(),
StanzaType.chat, commandsHelp(),
"Commands description", null,
packet.getStanzaId()));
break;
case update :
updatePost(packet, Long.parseLong(body_split[1]));
results.offer(Message.getMessage(packet.getStanzaTo(), packet.getStanzaFrom(),
StanzaType.normal, "Post " + body_split[1] + " successfuly updated.",
"Command execution result", null, packet.getStanzaId()));
break;
case delete :
deletePost(Long.parseLong(body_split[1]));
results.offer(Message.getMessage(packet.getStanzaTo(), packet.getStanzaFrom(),
StanzaType.normal, "Post " + body_split[1] + " successfuly deleted.",
"Command execution result", null, packet.getStanzaId()));
break;
default :
break;
}
} catch (Exception e) {
String error_text = "Hm, something wrong with command executing...: "
+ body_split[0] + ", " + body_split[1] + ", " + e;
log.log(Level.WARNING, error_text, e);
results.offer(Message.getMessage(packet.getStanzaTo(), packet.getStanzaFrom(),
StanzaType.normal, error_text,
"Problem with command execution", null,
packet.getStanzaId()));
}
}
private void updatePost(Packet packet, long snid) throws SQLException {
checkConnection();
String subject = packet.getElemCData("/message/subject");
String body = packet.getElemCData("/message/body");
if ((body != null) && (subject != null)) {
int idx = body.indexOf('\n');
if ((idx > 0) && (idx < body.length() - 1) && (body.charAt(idx + 1) == '\r')) {
++idx;
}
update_post.setString(1, XMLUtils.unescape(subject));
update_post.setString(2, XMLUtils.unescape(body.substring(idx)));
update_post.setLong(3, snid);
update_post.executeUpdate();
}
}
}
//~ Formatted in Sun Code Convention
//~ Formatted by Jindent --- http://www.jindent.com