package org.buddycloud.channelserver.db.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.buddycloud.channelserver.Configuration;
import org.buddycloud.channelserver.channel.Conf;
import org.buddycloud.channelserver.channel.LocalDomainChecker;
import org.buddycloud.channelserver.db.ClosableIteratorImpl;
import org.buddycloud.channelserver.db.CloseableIterator;
import org.buddycloud.channelserver.db.NodeStore;
import org.buddycloud.channelserver.db.exception.ItemNotFoundException;
import org.buddycloud.channelserver.db.exception.NodeStoreException;
import org.buddycloud.channelserver.pubsub.accessmodel.AccessModels;
import org.buddycloud.channelserver.pubsub.affiliation.Affiliations;
import org.buddycloud.channelserver.pubsub.model.GlobalItemID;
import org.buddycloud.channelserver.pubsub.model.NodeAffiliation;
import org.buddycloud.channelserver.pubsub.model.NodeItem;
import org.buddycloud.channelserver.pubsub.model.NodeMembership;
import org.buddycloud.channelserver.pubsub.model.NodeMembershipWithConfiguration;
import org.buddycloud.channelserver.pubsub.model.NodeSubscription;
import org.buddycloud.channelserver.pubsub.model.NodeThread;
import org.buddycloud.channelserver.pubsub.model.impl.GlobalItemIDImpl;
import org.buddycloud.channelserver.pubsub.model.impl.NodeAffiliationImpl;
import org.buddycloud.channelserver.pubsub.model.impl.NodeItemImpl;
import org.buddycloud.channelserver.pubsub.model.impl.NodeMembershipImpl;
import org.buddycloud.channelserver.pubsub.model.impl.NodeMembershipWithConfigurationImpl;
import org.buddycloud.channelserver.pubsub.model.impl.NodeSubscriptionImpl;
import org.buddycloud.channelserver.pubsub.model.impl.NodeThreadImpl;
import org.buddycloud.channelserver.pubsub.subscription.Subscriptions;
import org.xmpp.packet.JID;
import org.xmpp.resultsetmanagement.ResultSet;
import org.xmpp.resultsetmanagement.ResultSetImpl;
public class JDBCNodeStore implements NodeStore {
private static final Logger LOGGER = Logger.getLogger(JDBCNodeStore.class);
private final Connection conn;
private final NodeStoreSQLDialect dialect;
private final Deque<JDBCTransaction> transactionStack;
private boolean transactionHasBeenRolledBack = false;
private Configuration configuration;
/**
* Create a new node store connection backed by the given JDBC {@link Connection}.
*
* @param conn the connection to the backing database.
* @param configuration
*/
public JDBCNodeStore(final Connection conn, final NodeStoreSQLDialect dialect, Configuration configuration) {
this.conn = conn;
this.dialect = dialect;
this.configuration = configuration;
transactionStack = new ArrayDeque<JDBCTransaction>();
}
@Override
public void createNode(JID owner, String nodeId, Map<String, String> nodeConf) throws NodeStoreException {
if (owner == null) {
throw new NullPointerException("owner must not be null");
}
if (nodeId == null) {
throw new NullPointerException("nodeId must not be null");
}
PreparedStatement addStatement = null;
try {
// Store node
addStatement = conn.prepareStatement(dialect.insertNode());
addStatement.setString(1, nodeId);
addStatement.executeUpdate();
addStatement.close();
// Store the config (if there is any)
if (nodeConf != null) {
setNodeConf(nodeId, nodeConf);
}
NodeSubscriptionImpl subscription = new NodeSubscriptionImpl(nodeId, owner, Subscriptions.subscribed, null);
addUserSubscription(subscription);
setUserAffiliation(nodeId, owner, Affiliations.owner);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(addStatement);
}
}
@Override
public void deleteNode(String nodeId) throws NodeStoreException {
PreparedStatement deleteStatement = null;
try {
deleteStatement = conn.prepareStatement(dialect.deleteNode());
deleteStatement.setString(1, nodeId);
deleteStatement.executeUpdate();
deleteStatement.close();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(deleteStatement);
}
}
@Override
public void addRemoteNode(String nodeId) throws NodeStoreException {
if (null == nodeId) {
throw new NullPointerException("nodeId must not be null");
}
PreparedStatement addStatement = null;
try {
addStatement = conn.prepareStatement(dialect.insertNode());
addStatement.setString(1, nodeId);
addStatement.executeUpdate();
addStatement.close();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(addStatement);
}
}
@Override
public void setNodeConfValue(String nodeId, String key, String value) throws NodeStoreException {
if (nodeId == null) {
throw new NullPointerException("nodeId must not be null");
}
if (key == null) {
throw new NullPointerException("key must not be null");
}
PreparedStatement updateStatement = null;
PreparedStatement addStatement = null;
try {
updateStatement = conn.prepareStatement(dialect.updateNodeConf());
updateStatement.setString(1, value);
updateStatement.setString(2, nodeId);
updateStatement.setString(3, key);
int rows = updateStatement.executeUpdate();
updateStatement.close();
if (rows == 0) { // If the update didn't update any rows
addStatement = conn.prepareStatement(dialect.insertConf());
addStatement.setString(1, nodeId);
addStatement.setString(2, key);
addStatement.setString(3, value);
addStatement.executeUpdate();
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(updateStatement);
close(addStatement);
}
}
@Override
public void setNodeConf(String nodeId, Map<String, String> conf) throws NodeStoreException {
Transaction t = null;
try {
t = beginTransaction();
for (final Entry<String, String> entry : conf.entrySet()) {
setNodeConfValue(nodeId, entry.getKey(), entry.getValue());
}
t.commit();
} catch (NodeStoreException e) {
throw new NodeStoreException(e);
} finally {
close(t);
}
}
@Override
public boolean nodeExists(String nodeId) throws NodeStoreException {
PreparedStatement existsStatement = null;
try {
existsStatement = conn.prepareStatement(dialect.nodeExists());
existsStatement.setString(1, nodeId);
java.sql.ResultSet rs = existsStatement.executeQuery();
boolean exists = rs.next();
rs.close();
existsStatement.close();
return exists;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(existsStatement); // Will implicitly close the resultset if
// required
}
}
@Override
public void setUserAffiliation(String nodeId, JID user, Affiliations affiliation) throws NodeStoreException {
PreparedStatement deleteStatement = null;
PreparedStatement updateStatement = null;
PreparedStatement addStatement = null;
Transaction t = null;
try {
t = beginTransaction();
if (affiliation.equals(Affiliations.none)) {
deleteStatement = conn.prepareStatement(dialect.deleteAffiliation());
deleteStatement.setString(1, nodeId);
deleteStatement.setString(2, user.toBareJID());
deleteStatement.executeUpdate();
deleteStatement.close();
} else {
updateStatement = conn.prepareStatement(dialect.updateAffiliation());
updateStatement.setString(1, affiliation.toString());
updateStatement.setString(2, nodeId);
updateStatement.setString(3, user.toBareJID());
int rows = updateStatement.executeUpdate();
updateStatement.close();
if (rows == 0) { // If the update didn't update any rows
addStatement = conn.prepareStatement(dialect.insertAffiliation());
addStatement.setString(1, nodeId);
addStatement.setString(2, user.toBareJID());
addStatement.setString(3, affiliation.toString());
addStatement.executeUpdate();
addStatement.close();
}
}
t.commit();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(deleteStatement);
close(updateStatement);
close(addStatement);
close(t);
}
}
@Override
public String getNodeConfValue(String nodeId, String key) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectSingleNodeConfValue());
stmt.setString(1, nodeId);
stmt.setString(2, key);
java.sql.ResultSet rs = stmt.executeQuery();
String result = null;
if (rs.next()) {
result = rs.getString(1);
}
return result;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void deleteNodeConfiguration(String nodeId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.deleteConfFromNode());
stmt.setString(1, nodeId);
stmt.executeUpdate();
stmt.close();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public Map<String, String> getNodeConf(String nodeId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectNodeConf());
stmt.setString(1, nodeId);
java.sql.ResultSet rs = stmt.executeQuery();
HashMap<String, String> result = new HashMap<String, String>();
while (rs.next()) {
result.put(rs.getString(1), rs.getString(2));
}
return result;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void addUserSubscription(final NodeSubscription subscription) throws NodeStoreException {
PreparedStatement deleteStatement = null;
PreparedStatement updateStatement = null;
PreparedStatement addStatement = null;
Transaction t = null;
try {
if (subscription.getSubscription().equals(Subscriptions.none)) {
deleteStatement = conn.prepareStatement(dialect.deleteSubscription());
deleteStatement.setString(1, subscription.getNodeId());
deleteStatement.setString(2, subscription.getUser().toBareJID());
deleteStatement.executeUpdate();
deleteStatement.close();
} else {
t = beginTransaction();
updateStatement = conn.prepareStatement(dialect.updateSubscription());
updateStatement.setString(1, subscription.getSubscription().toString());
updateStatement.setString(2, subscription.getListener().toString());
updateStatement.setString(3, subscription.getNodeId());
updateStatement.setString(4, subscription.getUser().toBareJID());
int rows = updateStatement.executeUpdate();
updateStatement.close();
if (rows == 0) { // If the update didn't update any rows
addStatement = conn.prepareStatement(dialect.insertSubscription());
addStatement.setString(1, subscription.getNodeId());
addStatement.setString(2, subscription.getUser().toBareJID());
addStatement.setString(3, subscription.getListener().toString());
addStatement.setString(4, subscription.getSubscription().toString());
if (null == subscription.getInvitedBy()) {
addStatement.setNull(5, Types.NULL);
} else {
addStatement.setString(5, subscription.getInvitedBy().toBareJID());
}
addStatement.executeUpdate();
addStatement.close();
}
t.commit();
}
} catch (SQLException e) {
LOGGER.debug("Error adding new subscription: " + e.getMessage(), e);
throw new NodeStoreException(e);
} finally {
close(deleteStatement);
close(updateStatement);
close(addStatement);
close(t);
}
}
@Override
public ResultSet<NodeAffiliation> getAffiliationChanges(JID user, Date startDate, Date endDate) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectAffiliationChanges());
stmt.setString(3, user.toBareJID());
stmt.setTimestamp(1, new java.sql.Timestamp(startDate.getTime()));
stmt.setTimestamp(2, new java.sql.Timestamp(endDate.getTime()));
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeAffiliation> result = new ArrayList<NodeAffiliation>();
while (rs.next()) {
NodeAffiliationImpl nodeSub =
new NodeAffiliationImpl(rs.getString(1), new JID(rs.getString(2)), Affiliations.valueOf(rs.getString(3)), rs.getTimestamp(4));
result.add(nodeSub);
}
return new ResultSetImpl<NodeAffiliation>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ResultSet<NodeMembership> getUserMemberships(JID jid) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectUserMemberships());
stmt.setString(1, jid.toBareJID());
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeMembership> result = new ArrayList<NodeMembership>();
while (rs.next()) {
JID inviter = null;
if (null != rs.getString(6)) {
inviter = new JID(rs.getString(6));
}
NodeMembershipImpl membership =
new NodeMembershipImpl(rs.getString(1), new JID(rs.getString(2)), new JID(rs.getString(3)),
Subscriptions.valueOf(rs.getString(4)), Affiliations.valueOf(rs.getString(5)), inviter, rs.getTimestamp(7));
result.add(membership);
}
return new ResultSetImpl<NodeMembership>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ResultSet<NodeMembershipWithConfiguration> getUserMembershipsWithConfiguration(JID jid, List<String> configurationFilter,
Map<String, String> subscriptionsFilter) throws NodeStoreException {
PreparedStatement stmt = null;
try {
String sql = dialect.selectUserMembershipsWithConfiguration();
sql = sql.replace("%configFilter%", getCollectionStatement(configurationFilter.size()));
sql = sql.replace("%subscriptionFilter%", getCollectionStatement(subscriptionsFilter.size()));
stmt = conn.prepareStatement(sql);
int parameterIdx = 1;
for (Entry<String, String> subscriptionFilterEntry : subscriptionsFilter.entrySet()) {
stmt.setString(parameterIdx++, subscriptionFilterEntry.getKey() + ";" + subscriptionFilterEntry.getValue());
}
stmt.setInt(parameterIdx++, subscriptionsFilter.size());
stmt.setInt(parameterIdx++, configurationFilter.size());
for (String configurationValue : configurationFilter) {
stmt.setString(parameterIdx++, configurationValue);
}
stmt.setString(parameterIdx, jid.toBareJID());
java.sql.ResultSet rs = stmt.executeQuery();
Map<String, NodeMembershipWithConfiguration> memberships = new HashMap<String, NodeMembershipWithConfiguration>();
while (rs.next()) {
String nodeId = rs.getString(1);
NodeMembershipWithConfigurationImpl membership = (NodeMembershipWithConfigurationImpl) memberships.get(nodeId);
if (membership == null) {
JID inviter = null;
if (null != rs.getString(6)) {
inviter = new JID(rs.getString(6));
}
membership = new NodeMembershipWithConfigurationImpl(new NodeMembershipImpl(nodeId,
new JID(rs.getString(2)), new JID(rs.getString(3)),
Subscriptions.valueOf(rs.getString(4)), Affiliations.valueOf(rs.getString(5)),
inviter, rs.getTimestamp(7)), new HashMap<String, String>());
memberships.put(nodeId, membership);
}
membership.putConfiguration(rs.getString(8), rs.getString(9));
}
return new ResultSetImpl<NodeMembershipWithConfiguration>(memberships.values());
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
private static String getCollectionStatement(int collectionLength) {
if (collectionLength == 0) {
return "'%'";
}
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < collectionLength; i++) {
if (i > 0) {
strBuilder.append(",");
}
strBuilder.append("?");
}
return strBuilder.toString();
}
@Override
public ResultSet<NodeMembership> getUserMemberships(JID jid, boolean ephemeral) throws NodeStoreException {
PreparedStatement stmt = null;
try {
String sql = dialect.selectUserMembershipsFilteredByEphemeral();
String replace = "IS NULL OR \"node_config\".\"value\" != 'true'";
if (ephemeral) {
replace = "= 'true'";
}
stmt = conn.prepareStatement(sql.replace("%equals%", replace));
stmt.setString(1, jid.toBareJID());
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeMembership> result = new ArrayList<NodeMembership>();
while (rs.next()) {
JID inviter = null;
if (null != rs.getString(6)) {
inviter = new JID(rs.getString(6));
}
NodeMembershipImpl membership =
new NodeMembershipImpl(rs.getString(1), new JID(rs.getString(2)), new JID(rs.getString(3)),
Subscriptions.valueOf(rs.getString(4)), Affiliations.valueOf(rs.getString(5)), inviter, rs.getTimestamp(7));
result.add(membership);
}
return new ResultSetImpl<NodeMembership>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ArrayList<JID> getNodeOwners(String node) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectNodeOwners());
stmt.setString(1, node);
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<JID> result = new ArrayList<JID>();
while (rs.next()) {
result.add(new JID(rs.getString(1)));
}
return result;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public NodeMembership getNodeMembership(String nodeId, JID user) throws NodeStoreException {
PreparedStatement selectStatement = null;
try {
NodeMembershipImpl membership;
selectStatement = conn.prepareStatement(dialect.selectMembership());
selectStatement.setString(2, nodeId);
selectStatement.setString(1, user.toBareJID());
java.sql.ResultSet rs = selectStatement.executeQuery();
if (rs.next()) {
JID inviter = null;
if (null != rs.getString(6)) {
inviter = new JID(rs.getString(6));
}
membership =
new NodeMembershipImpl(nodeId, new JID(rs.getString(2)), new JID(rs.getString(3)), Subscriptions.valueOf(rs.getString(4)),
Affiliations.valueOf(rs.getString(5)), inviter, rs.getTimestamp(7));
} else {
membership = new NodeMembershipImpl(nodeId, user, user, Subscriptions.none, Affiliations.none, null);
}
return membership;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(selectStatement); // Will implicitly close the resultset if
// required
}
}
@Override
public ResultSet<NodeMembership> getNodeMemberships(String nodeId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectNodeMemberships());
stmt.setString(1, nodeId);
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeMembership> result = new ArrayList<NodeMembership>();
while (rs.next()) {
JID inviter = null;
if (null != rs.getString(6)) {
inviter = new JID(rs.getString(6));
}
NodeMembershipImpl membership =
new NodeMembershipImpl(rs.getString(1), new JID(rs.getString(2)), new JID(rs.getString(3)),
Subscriptions.valueOf(rs.getString(4)), Affiliations.valueOf(rs.getString(5)), inviter, rs.getTimestamp(7));
result.add(membership);
}
return new ResultSetImpl<NodeMembership>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public CloseableIterator<NodeItem> getFirehose(int limit, String afterItemId, boolean isAdmin, String actorDomain) throws NodeStoreException {
PreparedStatement stmt = null;
Date beforeDate = null;
if (afterItemId != null) {
beforeDate = getNodeItem(GlobalItemIDImpl.fromBuddycloudString(afterItemId)).getUpdated();
} else {
beforeDate = new Date();
}
if (limit < 0) {
throw new IllegalArgumentException("Invalid value for parameter count: " + limit);
}
try {
stmt = conn.prepareStatement(dialect.selectItemsForLocalNodesBeforeDate());
stmt.setTimestamp(1, new java.sql.Timestamp(beforeDate.getTime()));
stmt.setString(2, Conf.ACCESS_MODEL);
stmt.setBoolean(3, isAdmin);
stmt.setString(4, AccessModels.open.toString());
stmt.setString(5, AccessModels.local.toString());
stmt.setString(6, getDomainRegex(actorDomain));
stmt.setBoolean(7, isAdmin);
stmt.setString(8, getLocalDomainRegex());
stmt.setInt(9, limit);
java.sql.ResultSet rs = stmt.executeQuery();
LinkedList<NodeItem> results = new LinkedList<NodeItem>();
while (rs.next()) {
results.push(new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(6)));
}
return new ClosableIteratorImpl<NodeItem>(results.iterator());
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public int getFirehoseItemCount(boolean isAdmin, String actorDomain) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.countItemsForLocalNodes());
stmt.setString(1, Conf.ACCESS_MODEL);
stmt.setBoolean(2, isAdmin);
stmt.setString(3, AccessModels.open.toString());
stmt.setString(4, AccessModels.local.toString());
stmt.setString(5, getDomainRegex(actorDomain));
stmt.setBoolean(6, isAdmin);
stmt.setString(7, getLocalDomainRegex());
java.sql.ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
return 0; // This really shouldn't happen!
}
return rs.getInt(1);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if
// required
}
}
private static final String POSIX_SPECIAL_CHARS = "\\.^$*+?()[{|";
private static String posixRegexQuote(String str) {
for (Character p : POSIX_SPECIAL_CHARS.toCharArray()) {
str = str.replace(p.toString(), "\\" + p.toString());
}
return str;
}
private static String getLocalDomainRegex() {
String serverDomain = Configuration.getInstance().getServerDomain();
String serverTopicsDomain = Configuration.getInstance().getServerTopicsDomain();
List<String> localDomains = new LinkedList<String>();
if (serverDomain != null) {
localDomains.add(posixRegexQuote(serverDomain));
}
if (serverTopicsDomain != null) {
localDomains.add(posixRegexQuote(serverTopicsDomain));
}
for (String localDomain : LocalDomainChecker.getLocalDomains(Configuration.getInstance())) {
localDomains.add(posixRegexQuote(localDomain));
}
String domainRegex = localDomains.isEmpty() ? ".*" : getDomainRegex(StringUtils.join(localDomains, "|"));
return domainRegex;
}
private static String getDomainRegex(String localDomains) {
return ".*@(" + localDomains + ")\\/.*";
}
@Override
public List<String> getLocalNodesList() throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectLocalNodes());
stmt.setString(1, getLocalDomainRegex());
java.sql.ResultSet rs = stmt.executeQuery();
List<String> result = new ArrayList<String>();
while (rs.next()) {
result.add(rs.getString(1));
}
return result;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public List<String> getRemoteNodesList() throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectRemoteNodes());
stmt.setString(1, getLocalDomainRegex());
java.sql.ResultSet rs = stmt.executeQuery();
List<String> result = new ArrayList<String>();
while (rs.next()) {
result.add(rs.getString(1));
}
return result;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ResultSet<NodeSubscription> getSubscriptionChanges(JID user, Date startDate, Date endDate) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.getSubscriptionChanges());
stmt.setTimestamp(1, new java.sql.Timestamp(startDate.getTime()));
stmt.setTimestamp(2, new java.sql.Timestamp(endDate.getTime()));
stmt.setString(3, user.toBareJID());
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeSubscription> result = new ArrayList<NodeSubscription>();
while (rs.next()) {
JID invitedBy = null;
if (null != rs.getString(5)) {
invitedBy = new JID(rs.getString(5));
}
NodeSubscriptionImpl nodeSub =
new NodeSubscriptionImpl(rs.getString(1), new JID(rs.getString(2)), new JID(rs.getString(3)), Subscriptions.valueOf(rs
.getString(4)), invitedBy, rs.getTimestamp(6));
result.add(nodeSub);
}
return new ResultSetImpl<NodeSubscription>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public int getCountLocalSubscriptionsToNode(String node) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.countLocalValidSubscriptionsForNode());
stmt.setString(1, node);
stmt.setString(2, "%@" + configuration.getServerDomain());
java.sql.ResultSet rs = stmt.executeQuery();
stmt = null; // Prevent the finally block from closing the
// statement
rs.next();
return rs.getInt("count");
} catch (SQLException e) {
LOGGER.error(e);
throw new NodeStoreException(e);
}
}
@Override
public ResultSet<NodeSubscription> getNodeSubscriptionListeners(String nodeId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectSubscriptionListenersForNode());
stmt.setString(1, nodeId);
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeSubscription> result = new ArrayList<NodeSubscription>();
while (rs.next()) {
NodeSubscriptionImpl nodeSub =
new NodeSubscriptionImpl(rs.getString(2), new JID(rs.getString(1)), Subscriptions.valueOf(rs.getString(3)), null,
rs.getTimestamp(4));
result.add(nodeSub);
}
return new ResultSetImpl<NodeSubscription>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ResultSet<NodeSubscription> getNodeSubscriptionListeners() throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectSubscriptionListeners());
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeSubscription> result = new ArrayList<NodeSubscription>();
while (rs.next()) {
NodeSubscriptionImpl nodeSub =
new NodeSubscriptionImpl(rs.getString(2), new JID(rs.getString(1)), Subscriptions.valueOf(rs.getString(3)), null,
rs.getTimestamp(4));
result.add(nodeSub);
}
return new ResultSetImpl<NodeSubscription>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public CloseableIterator<NodeItem> getNodeItems(String nodeId, String afterItemId, int count, boolean parentOnly) throws NodeStoreException {
NodeItem afterItem = null;
PreparedStatement stmt = null;
if (afterItemId != null) {
afterItem = getNodeItem(nodeId, afterItemId);
}
String countSQL = "";
if (count > -1) {
countSQL = " OFFSET 0 LIMIT " + count;
} else if (count < -1) {
throw new IllegalArgumentException("Invalid value for parameter count: " + count);
}
String parentOnlySubstitution = "";
if (parentOnly) {
parentOnlySubstitution = " AND \"in_reply_to\" IS NULL ";
}
String query = null;
try {
if (afterItem == null) {
query = dialect.selectItemsForNode()
.replace("%parentOnly%", parentOnlySubstitution) + countSQL;
stmt = conn.prepareStatement(query);
stmt.setString(1, nodeId);
java.sql.ResultSet rs = stmt.executeQuery();
stmt = null; // Prevent the finally block from closing the
// statement
return new ResultSetIterator<NodeItem>(rs, new ResultSetIterator.RowConverter<NodeItem>() {
@Override
public NodeItem convertRow(java.sql.ResultSet rs) throws SQLException {
return new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(6));
}
});
} else {
query = dialect.selectItemsForNodeBeforeDate()
.replace("%parentOnly%", parentOnlySubstitution) + countSQL;
stmt = conn.prepareStatement(query);
stmt.setString(1, nodeId);
stmt.setTimestamp(2, new java.sql.Timestamp(afterItem.getUpdated().getTime()));
stmt.setTimestamp(3, new java.sql.Timestamp(afterItem.getUpdated().getTime()));
stmt.setString(4, getLocalId(afterItemId));
java.sql.ResultSet rs = stmt.executeQuery();
return new ResultSetIterator<NodeItem>(rs, new ResultSetIterator.RowConverter<NodeItem>() {
@Override
public NodeItem convertRow(java.sql.ResultSet rs) throws SQLException {
return new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(6));
}
});
}
} catch (SQLException e) {
throw new NodeStoreException(e);
}
}
@Override
public CloseableIterator<NodeItem> getUserFeedItems(JID user, Date since, int limit, GlobalItemID afterItemId, boolean parentOnly)
throws NodeStoreException {
PreparedStatement stmt = null;
String limitSQL = "";
String afterSQL = "";
String parentSQL = "";
Date afterItemDate = null;
if (limit > -1) {
limitSQL = " LIMIT " + limit;
} else if (limit < -1) {
throw new IllegalArgumentException("Invalid value for parameter count: " + limit);
}
if (true == parentOnly) {
parentSQL = " AND \"in_reply_to\" IS NULL ";
}
if (afterItemId != null) {
afterItemDate = getNodeItem(afterItemId).getUpdated();
afterSQL = " AND \"updated\" < ? ";
}
try {
stmt =
conn.prepareStatement(dialect.selectUserFeedItems().replace("%limit%", limitSQL).replace("%after%", afterSQL)
.replace("%parent%", parentSQL));
stmt.setString(1, user.toBareJID());
stmt.setObject(2, new java.sql.Timestamp(since.getTime()));
if (afterItemId != null) {
stmt.setTimestamp(3, new java.sql.Timestamp(afterItemDate.getTime()));
}
java.sql.ResultSet rs = stmt.executeQuery();
stmt = null; // Prevent the finally block from closing the
// statement
return new ResultSetIterator<NodeItem>(rs, new ResultSetIterator.RowConverter<NodeItem>() {
@Override
public NodeItem convertRow(java.sql.ResultSet rs) throws SQLException {
return new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5));
}
});
} catch (SQLException e) {
throw new NodeStoreException(e);
}
}
@Override
public int getCountUserFeedItems(JID user, Date since, boolean parentOnly) throws NodeStoreException {
PreparedStatement stmt = null;
String parentSQL = "";
if (true == parentOnly) {
parentSQL = " AND \"in_reply_to\" IS NULL ";
}
try {
stmt = conn.prepareStatement(dialect.selectCountUserFeedItems().replace("%parent%", parentSQL));
stmt.setString(1, user.toBareJID());
stmt.setObject(2, new java.sql.Timestamp(since.getTime()));
java.sql.ResultSet rs = stmt.executeQuery();
stmt = null; // Prevent the finally block from closing the
// statement
rs.next();
return rs.getInt("count");
} catch (SQLException e) {
LOGGER.error(e);
throw new NodeStoreException(e);
}
}
@Override
public int getCountRecentItems(JID user, Date since, int maxPerNode, String node, boolean parentOnly) throws NodeStoreException {
if (null == node) {
node = "/posts";
}
if (-1 == maxPerNode) {
maxPerNode = Integer.MAX_VALUE;
}
String queryPart;
String parentOnlyReplacement;
PreparedStatement stmt = null;
try {
ResultSet<NodeMembership> subscriptions = this.getUserMemberships(user);
ArrayList<String> queryParts = new ArrayList<String>();
ArrayList<Object> parameters = new ArrayList<Object>();
for (NodeMembership subscription : subscriptions) {
if (false == subscription.getSubscription().equals(Subscriptions.subscribed)) {
continue;
}
if (false == subscription.getNodeId().substring(subscription.getNodeId().length() - node.length()).equals(node)) {
continue;
}
queryPart = dialect.selectCountRecentItemParts();
parentOnlyReplacement = "";
if (true == parentOnly) {
parentOnlyReplacement = "AND \"in_reply_to\" IS NULL";
}
queryPart = queryPart.replace("%parentOnly%", parentOnlyReplacement);
queryParts.add(queryPart);
parameters.add(subscription.getNodeId());
parameters.add(new java.sql.Timestamp(since.getTime()));
parameters.add(maxPerNode);
}
stmt = conn.prepareStatement(StringUtils.join(queryParts, " UNION ALL "));
int index = 1;
for (Object parameter : parameters) {
stmt.setObject(index, parameter);
++index;
}
java.sql.ResultSet rs = stmt.executeQuery();
int count = 0;
while (rs.next()) {
count += rs.getInt(1);
}
stmt = null; // Prevent the finally block from closing the
// statement
return count;
} catch (SQLException e) {
LOGGER.error(e);
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public CloseableIterator<NodeItem> getRecentItems(JID user, Date since, int maxPerNode, int limit, GlobalItemID afterItemId, String node,
boolean parentOnly) throws NodeStoreException {
PreparedStatement stmt = null;
try {
if (null == node) {
node = "/posts";
}
if (-1 == limit) {
limit = 50;
}
if (-1 == maxPerNode) {
maxPerNode = Integer.MAX_VALUE;
}
String queryPart;
String parentOnlyReplacement;
ResultSet<NodeMembership> subscriptions = this.getUserMemberships(user);
if (subscriptions == null || subscriptions.isEmpty()) {
return new ClosableIteratorImpl<NodeItem>(
new ArrayList<NodeItem>().iterator());
}
ArrayList<String> queryParts = new ArrayList<String>();
ArrayList<Object> parameters = new ArrayList<Object>();
int counter = 1;
for (NodeMembership subscription : subscriptions) {
if (false == subscription.getSubscription().equals(Subscriptions.subscribed)) {
continue;
}
if (false == subscription.getNodeId().endsWith(node)) {
continue;
}
queryPart = dialect.selectRecentItemParts();
parentOnlyReplacement = "";
if (true == parentOnly) {
parentOnlyReplacement = "AND \"in_reply_to\" IS NULL ";
}
queryPart = queryPart.replace("%parentOnly%", parentOnlyReplacement);
queryPart = queryPart.replace("%counter%", String.valueOf(counter));
queryParts.add(queryPart);
parameters.add(subscription.getNodeId());
parameters.add(new java.sql.Timestamp(since.getTime()));
parameters.add(maxPerNode);
++counter;
}
String sql = "SELECT * FROM (" + StringUtils.join(queryParts, " UNION ALL ") + ") AS recentItemsQuery";
if (afterItemId != null) {
NodeItem afterItem = getNodeItem(afterItemId.getNodeID(), afterItemId.getItemID());
if (afterItem != null) {
sql += " WHERE \"updated\" < ? OR" + " ( \"updated\" = ? AND" + " ( \"node\" > ? OR" + " ( \"node\" = ? AND \"id\" > ? ) ) )";
parameters.add(new Timestamp(afterItem.getUpdated().getTime()));
parameters.add(new Timestamp(afterItem.getUpdated().getTime()));
parameters.add(afterItem.getNodeId());
parameters.add(afterItem.getNodeId());
parameters.add(afterItem.getId());
}
}
sql += " ORDER BY \"updated\" DESC, \"node\" ASC, \"id\" ASC LIMIT ?;";
stmt = conn.prepareStatement(sql.toString());
int index = 1;
for (Object parameter : parameters) {
stmt.setObject(index, parameter);
++index;
}
stmt.setInt(index, limit);
java.sql.ResultSet rs = stmt.executeQuery();
stmt = null; // Prevent the finally block from closing the
// statement
ArrayList<NodeItem> results = new ArrayList<NodeItem>();
while (rs.next()) {
results.add(new NodeItemImpl(rs.getString(2), rs.getString(1), rs.getTimestamp(4), rs.getString(3), rs.getString(5), rs.getTimestamp(6)));
}
return new ClosableIteratorImpl<NodeItem>(results.iterator());
} catch (SQLException e) {
e.printStackTrace();
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public CloseableIterator<NodeItem> getNodeItems(String nodeId) throws NodeStoreException {
return getNodeItems(nodeId, null, -1, false);
}
@Override
public ClosableIteratorImpl<NodeItem> getNodeItemReplies(String nodeId, String itemId, String rsmItem, boolean after, int limit) throws NodeStoreException {
PreparedStatement stmt = null;
try {
Date since = new Date();
String afterReplacement = "<";
if (after) {
since = new Date(0);
afterReplacement = ">";
}
if (null != rsmItem) {
since = getNodeItem(nodeId, rsmItem).getUpdated();
}
String query = dialect.selectItemReplies()
.replace("%beforeAfter%", afterReplacement);
if (-1 != limit) {
query += " LIMIT ?";
}
stmt = conn.prepareStatement(query);
stmt.setString(1, nodeId);
stmt.setString(2, "%" + getLocalId(itemId));
stmt.setTimestamp(3, new java.sql.Timestamp(since.getTime()));
if (-1 != limit) {
stmt.setInt(4, limit);
}
java.sql.ResultSet rs = stmt.executeQuery();
LinkedList<NodeItem> results = new LinkedList<NodeItem>();
while (rs.next()) {
results.push(new NodeItemImpl(rs.getString(2), rs.getString(1), rs.getTimestamp(4), rs.getString(3), rs.getString(5), rs.getTimestamp(6)));
}
return new ClosableIteratorImpl<NodeItem>(results.iterator());
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public int getCountNodeItemReplies(String nodeId, String itemId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectCountItemReplies());
stmt.setString(1, nodeId);
stmt.setString(2, "%" + getLocalId(itemId));
java.sql.ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return rs.getInt(1);
} else {
return 0; // This really shouldn't happen!
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ClosableIteratorImpl<NodeItem> getNodeItemThread(String nodeId, String itemId, String afterItemId, int limit) throws NodeStoreException {
PreparedStatement stmt = null;
try {
Date since = new Date(0);
if (null != afterItemId) {
since = getNodeItem(nodeId, afterItemId).getUpdated();
}
String query = dialect.selectItemThread();
if (-1 != limit) {
query += " LIMIT ?";
}
stmt = conn.prepareStatement(query);
stmt.setString(1, nodeId);
itemId = getLocalId(itemId);
stmt.setString(2, "%" + itemId);
stmt.setString(3, itemId);
stmt.setTimestamp(4, new java.sql.Timestamp(since.getTime()));
if (-1 != limit) {
stmt.setInt(5, limit);
}
java.sql.ResultSet rs = stmt.executeQuery();
LinkedList<NodeItem> results = new LinkedList<NodeItem>();
while (rs.next()) {
results.push(new NodeItemImpl(rs.getString(2), rs.getString(1), rs.getTimestamp(4), rs.getString(3), rs.getString(5), rs.getTimestamp(6)));
}
return new ClosableIteratorImpl<NodeItem>(results.iterator());
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public int getCountNodeThread(String nodeId, String itemId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectCountItemThread());
stmt.setString(1, nodeId);
itemId = getLocalId(itemId);
stmt.setString(2, "%" + itemId);
stmt.setString(3, itemId);
java.sql.ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return rs.getInt(1);
} else {
return 0; // This really shouldn't happen!
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public CloseableIterator<NodeItem> getNewNodeItemsForUser(JID user, Date startDate, Date endDate) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectItemsForUsersNodesBetweenDates());
stmt.setString(3, user.toBareJID());
stmt.setTimestamp(1, new java.sql.Timestamp(startDate.getTime()));
stmt.setTimestamp(2, new java.sql.Timestamp(endDate.getTime()));
java.sql.ResultSet rs = stmt.executeQuery();
LinkedList<NodeItem> results = new LinkedList<NodeItem>();
while (rs.next()) {
results.push(new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(6)));
}
return new ClosableIteratorImpl<NodeItem>(results.iterator());
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public int countNodeItems(String nodeId, boolean parentOnly) throws NodeStoreException {
PreparedStatement selectStatement = null;
try {
String query = dialect.countItemsForNode();
String parentOnlySubstitution = "";
if (parentOnly) {
parentOnlySubstitution = "AND \"in_reply_to\" IS NULL";
}
selectStatement = conn.prepareStatement(
query.replace("%parentOnly%", parentOnlySubstitution)
);
selectStatement.setString(1, nodeId);
java.sql.ResultSet rs = selectStatement.executeQuery();
if (rs.next()) {
return rs.getInt(1);
} else {
return 0; // This really shouldn't happen!
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(selectStatement); // Will implicitly close the resultset if
// required
}
}
@Override
public NodeItem getNodeItem(GlobalItemID itemId) throws NodeStoreException {
return getNodeItem(itemId.getNodeID(), itemId.getItemID());
}
@Override
public NodeItem getNodeItem(String nodeId, String nodeItemId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectSingleItem());
stmt.setString(1, nodeId);
stmt.setString(2, getLocalId(nodeItemId));
java.sql.ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(6));
}
return null;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void addNodeItem(NodeItem item) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.insertItem());
stmt.setString(1, item.getNodeId());
stmt.setString(2, item.getId());
stmt.setTimestamp(3, new Timestamp(item.getUpdated().getTime()));
stmt.setString(4, item.getPayload());
stmt.setString(5, item.getInReplyTo());
stmt.executeUpdate();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void updateNodeItem(NodeItem item) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.updateItem());
stmt.setTimestamp(1, new Timestamp(item.getUpdated().getTime()));
stmt.setString(2, item.getPayload());
stmt.setString(3, item.getNodeId());
stmt.setString(4, item.getId());
int rows = stmt.executeUpdate();
if (rows != 1) {
throw new ItemNotFoundException("No records affected when updating an item");
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void updateThreadParent(String node, String itemId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.updateThreadParent());
stmt.setString(1, node);
stmt.setString(2, itemId);
stmt.executeUpdate();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void deleteNodeItemById(String nodeId, String nodeItemId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.deleteItem());
stmt.setString(1, nodeId);
stmt.setString(2, getLocalId(nodeItemId));
int rows = stmt.executeUpdate();
if (rows != 1) {
throw new ItemNotFoundException("No records affected when deleting an item");
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void purgeNodeItems(String nodeId) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.deleteItems());
stmt.setString(1, nodeId);
stmt.executeUpdate();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public CloseableIterator<NodeItem> performSearch(JID searcher, List content, JID author, int page, int rpp) throws NodeStoreException {
PreparedStatement stmt = null;
try {
String sql =
"SELECT * FROM \"items\" " + "LEFT JOIN \"subscriptions\" ON \"items\".\"node\" = \"subscriptions\".\"node\" " + "WHERE "
+ "\"subscriptions\".\"user\" = ? " + "AND \"subscriptions\".\"subscription\" = 'subscribed' "
+ "AND RIGHT(\"items\".\"node\", 6) = '/posts' " + " $searchParameters " + "ORDER BY \"items\".\"updated\" DESC "
+ "LIMIT ? OFFSET ?;";
ArrayList<String> parameterValues = new ArrayList<String>();
parameterValues.add(searcher.toBareJID());
String searchParameters = "";
for (String term : (List<String>) content) {
searchParameters += "AND (\"items\".\"xml\" LIKE ?) ";
parameterValues.add("%<content%>%" + term + "%</content>%");
}
if (null != author) {
searchParameters += "AND (\"items\".\"xml\" LIKE ?)";
parameterValues.add("%<name>" + author.toBareJID() + "</name>%");
}
stmt = conn.prepareStatement(sql.replace("$searchParameters", searchParameters));
int counter = 0;
for (String value : parameterValues) {
++counter;
stmt.setString(counter, value);
}
stmt.setInt(parameterValues.size() + 1, rpp);
stmt.setInt(parameterValues.size() + 2, (page - 1) * rpp);
java.sql.ResultSet rs = stmt.executeQuery();
stmt = null; // Prevent the finally block from closing the
// statement
return new ResultSetIterator<NodeItem>(rs, new ResultSetIterator.RowConverter<NodeItem>() {
@Override
public NodeItem convertRow(java.sql.ResultSet rs) throws SQLException {
return new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(6));
}
});
} catch (SQLException e) {
e.printStackTrace();
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ArrayList<String> getNodeList() throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectNodeList());
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<String> result = new ArrayList<String>();
while (rs.next()) {
result.add(rs.getString(1));
}
return result;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public boolean isCachedNode(String nodeId) throws NodeStoreException {
return ((this.countNodeItems(nodeId, false) > 0) && (true == this.isCachedNodeConfig(nodeId)));
}
@Override
public boolean isCachedNodeConfig(String nodeId) throws NodeStoreException {
return (this.getNodeConf(nodeId).size() > 0);
}
@Override
public boolean isCachedJID(JID jid) throws NodeStoreException {
PreparedStatement selectStatement = null;
try {
selectStatement = conn.prepareStatement(dialect.countSubscriptionsForJid());
selectStatement.setString(1, jid.toBareJID());
java.sql.ResultSet rs = selectStatement.executeQuery();
if (rs.next()) {
return (rs.getInt(1) > 0);
} else {
return false; // This really shouldn't happen!
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(selectStatement); // Will implicitly close the resultset if
// required
}
}
@Override
public boolean userHasRatedPost(String node, JID user, GlobalItemID id) throws NodeStoreException {
PreparedStatement selectStatement = null;
try {
selectStatement = conn.prepareStatement(dialect.selectUserRatingsForAPost());
selectStatement.setString(1, node);
selectStatement.setString(2, "%<uri>acct:" + user.toBareJID() + "</uri>%");
selectStatement.setString(3, "%<activity:target><id>" + id.toString() + "</id>%");
java.sql.ResultSet rs = selectStatement.executeQuery();
if (rs.next()) {
return true;
} else {
return false; // This really shouldn't happen!
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(selectStatement); // Will implicitly close the resultset if
// required
}
}
@Override
public boolean nodeHasSubscriptions(String nodeId) throws NodeStoreException {
return (this.getNodeMemberships(nodeId).size() > 0);
}
@Override
public ResultSet<NodeItem> getUserPublishedItems(JID userJid) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.getUserItems());
stmt.setString(1, userJid.toBareJID());
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeItem> result = new ArrayList<NodeItem>();
while (rs.next()) {
NodeItem nodeItem =
new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(6));
result.add(nodeItem);
}
return new ResultSetImpl<NodeItem>(result);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void deleteUserItems(JID userJid) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.deleteUserItems());
stmt.setString(1, userJid.toBareJID());
stmt.executeUpdate();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void deleteUserAffiliations(JID userJid) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.deleteUserAffiliations());
stmt.setString(1, userJid.toBareJID());
stmt.executeUpdate();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public void deleteUserSubscriptions(JID userJid) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.deleteUserSubscriptions());
stmt.setString(1, userJid.toBareJID());
stmt.executeUpdate();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ResultSet<NodeThread> getNodeThreads(String node, String afterId, int limit) throws NodeStoreException {
Date after = new Date();
if (afterId != null) {
NodeItem afterItem = getNodeItem(node, afterId);
if (afterItem != null) {
after = afterItem.getUpdated();
}
}
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectNodeThreads());
stmt.setString(1, node);
stmt.setTimestamp(2, new java.sql.Timestamp(after.getTime()));
stmt.setInt(3, limit);
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<NodeThread> nodeThreads = new ArrayList<NodeThread>();
NodeThreadImpl currentThread = null;
while (rs.next()) {
NodeItem nodeItem =
new NodeItemImpl(rs.getString(1), rs.getString(2), rs.getTimestamp(3), rs.getString(4), rs.getString(5), rs.getTimestamp(8));
String threadId = rs.getString(6);
Date threadUpdated = rs.getTimestamp(7);
if (currentThread == null || !threadId.equals(currentThread.getId())) {
NodeThreadImpl newThread = new NodeThreadImpl(threadId, threadUpdated);
nodeThreads.add(newThread);
currentThread = newThread;
}
currentThread.addItem(nodeItem);
}
return new ResultSetImpl<NodeThread>(nodeThreads);
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public int countNodeThreads(String node) throws NodeStoreException {
PreparedStatement selectStatement = null;
try {
selectStatement = conn.prepareStatement(dialect.countNodeThreads());
selectStatement.setString(1, node);
java.sql.ResultSet rs = selectStatement.executeQuery();
if (rs.next()) {
return rs.getInt(1);
} else {
return 0; // This really shouldn't happen!
}
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(selectStatement);
}
}
@Override
public void jidOnline(JID jid) throws NodeStoreException {
PreparedStatement addStatement = null;
try {
jidOffline(jid);
addStatement = conn.prepareStatement(dialect.addOnlineJid());
addStatement.setString(1, jid.toFullJID());
addStatement.executeUpdate();
addStatement.close();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(addStatement);
}
}
@Override
public void jidOffline(JID jid) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.deleteOnlineJid());
stmt.setString(1, jid.toFullJID());
stmt.executeUpdate();
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public ArrayList<JID> onlineJids(JID jid) throws NodeStoreException {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(dialect.selectOnlineResources());
stmt.setString(1, jid.toBareJID() + "%");
java.sql.ResultSet rs = stmt.executeQuery();
ArrayList<JID> result = new ArrayList<JID>();
while (rs.next()) {
result.add(new JID(rs.getString(1)));
}
return result;
} catch (SQLException e) {
throw new NodeStoreException(e);
} finally {
close(stmt); // Will implicitly close the resultset if required
}
}
@Override
public Transaction beginTransaction() throws NodeStoreException {
if (transactionHasBeenRolledBack) {
throw new IllegalStateException("The transaction has already been rolled back");
}
JDBCTransaction transaction;
try {
transaction = new JDBCTransaction(this);
} catch (SQLException e) {
throw new NodeStoreException(e);
}
return transaction;
}
private void close(final Statement stmt) {
if (stmt != null) {
try {
if (false == stmt.isClosed()) {
stmt.close();
// stmt.getConnection().close();
}
} catch (SQLException e) {
LOGGER.error("SQLException thrown while trying to close a statement", e);
}
}
}
private void close(final Transaction trans) throws NodeStoreException {
if (trans != null) {
trans.close();
}
}
public class JDBCTransaction implements Transaction {
private JDBCNodeStore store;
private boolean closed;
private JDBCTransaction(final JDBCNodeStore store) throws SQLException {
this.store = store;
closed = false;
if (store.transactionStack.isEmpty()) {
store.conn.setAutoCommit(false);
}
store.transactionStack.push(this);
}
@Override
public void commit() throws NodeStoreException {
if (closed) {
throw new IllegalStateException("Commit called on transaction that is already closed");
}
if (!isLatestTransaction()) {
throw new IllegalStateException("Commit called on transaction other than the innermost transaction");
}
if (store.transactionHasBeenRolledBack) {
throw new IllegalStateException("Commit called after inner transaction has already been rolled back");
}
store.transactionStack.pop();
closed = true;
try {
if (store.transactionStack.isEmpty()) {
store.conn.commit();
store.conn.setAutoCommit(true);
store.transactionHasBeenRolledBack = false;
}
} catch (SQLException e) {
throw new NodeStoreException(e);
}
}
@Override
public void close() throws NodeStoreException {
if (closed) {
return; // Do nothing nicely and silently
}
if (!isLatestTransaction()) {
throw new IllegalStateException("Close called on transaction other than the innermost transaction");
}
store.transactionStack.pop();
closed = true;
store.transactionHasBeenRolledBack = true;
try {
if (store.transactionStack.isEmpty()) {
store.conn.rollback();
store.conn.setAutoCommit(true);
store.transactionHasBeenRolledBack = false;
}
} catch (SQLException e) {
throw new NodeStoreException(e);
}
}
private boolean isLatestTransaction() {
return (store.transactionStack.peek() == this);
}
}
@Override
public void close() throws NodeStoreException {
try {
conn.close();
} catch (SQLException e) {
throw new NodeStoreException(e);
}
}
private String getLocalId(String nodeItemId) {
if (GlobalItemIDImpl.isGlobalId(nodeItemId)) {
nodeItemId = GlobalItemIDImpl.toLocalId(nodeItemId);
}
return nodeItemId;
}
public interface NodeStoreSQLDialect {
String insertNode();
String addOnlineJid();
String deleteOnlineJid();
String selectOnlineResources();
String selectNodeMemberships();
String selectUserMemberships();
String selectUserMembershipsFilteredByEphemeral();
String selectUserMembershipsWithConfiguration();
String selectMembership();
String selectNodeOwners();
String getUserItems();
String selectItemsForLocalNodesBeforeDate();
String countItemsForLocalNodes();
String selectRecentItemParts();
String countNodeAffiliations();
String selectRemoteNodes();
String selectLocalNodes();
String countNodeAffiliationsForOwner();
String countUserAffiliations();
String countSubscriptionsForNode();
String countLocalValidSubscriptionsForNode();
String countSubscriptionsToNodeForOwner();
String deleteItems();
String selectNodeList();
String deleteNode();
String countSubscriptionsForJid();
String insertConf();
String deleteConfFromNode();
String updateNodeConf();
String selectSingleNodeConfValue();
String selectNodeConf();
String selectAffiliation();
String selectAffiliationsForUser();
String selectAffiliationsForUserAfterNodeId();
String selectAffiliationsForNode();
String selectAffiliationsToNodeForOwner();
String selectAffiliationsForNodeAfterJid();
String selectAffiliationsToNodeForOwnerAfterJid();
String selectAffiliationChanges();
String insertAffiliation();
String updateAffiliation();
String deleteAffiliation();
String selectSubscriptionsForUser();
String selectSubscriptionsForUserAfterNode();
String getSubscriptionChanges();
String selectSubscriptionsToNodeForOwner();
String selectSubscriptionsForNode();
String selectSubscriptionsForNodeAfterJid();
String selectSubscriptionListeners();
String selectSubscriptionListenersForNode();
String insertSubscription();
String updateSubscription();
String deleteSubscription();
String nodeExists();
String selectSingleItem();
String selectItemsForNode();
String selectItemsForNodeAfterDate();
String selectItemsForNodeBeforeDate();
String selectItemsForUsersNodesBetweenDates();
String countItemsForNode();
String selectItemReplies();
String selectCountItemReplies();
String selectItemThread();
String selectCountItemThread();
String insertItem();
String updateItem();
String updateThreadParent();
String deleteItem();
String selectCountRecentItemParts();
String deleteUserItems();
String deleteUserAffiliations();
String deleteUserSubscriptions();
String selectNodeThreads();
String countNodeThreads();
String selectUserRatingsForAPost();
String selectUserFeedItems();
String selectCountUserFeedItems();
}
}