Commit 6671ad86 authored by Leon Roy's avatar Leon Roy Committed by leonroy

monitoring plugin integrating Jive Monitoring Plugin and Stefan Reuter's Open...

monitoring plugin integrating Jive Monitoring Plugin and Stefan Reuter's Open Archive plugin to give both XEP-0136 support and group as well as individual chat archiving.

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/branches/plugins@13318 b35dd754-fafc-0310-a699-88a17e54d16e
parent e868339a
TRUNCATE TABLE ofConversation;
INSERT INTO ofConversation
(conversationID, room, isExternal, startDate, lastActivity, messageCount)
SELECT conversationID, room, isExternal, startDate, lastActivity, messageCount
FROM entConversation;
TRUNCATE TABLE ofConParticipant;
INSERT INTO ofConParticipant
(conversationID, joinedDate, leftDate, bareJID, jidResource, nickname)
SELECT conversationID, joinedDate, leftDate, bareJID, jidResource, nickname
FROM entConParticipant;
TRUNCATE TABLE ofMessageArchive;
INSERT INTO ofMessageArchive
(conversationID, fromJID, toJID, sentDate, body)
SELECT conversationID, fromJID, toJID, sentDate, body
FROM entMessageArchive;
TRUNCATE TABLE ofRRDs;
INSERT INTO ofRRDs
(id, updatedDate, bytes)
SELECT id, updatedDate, bytes
FROM entRRDs;
TRUNCATE TABLE ofConversation;
INSERT INTO ofConversation
(conversationID, room, isExternal, startDate, lastActivity, messageCount)
SELECT conversationID, room, isExternal, startDate, lastActivity, messageCount
FROM entConversation;
TRUNCATE TABLE ofConParticipant;
INSERT INTO ofConParticipant
(conversationID, joinedDate, leftDate, bareJID, jidResource, nickname)
SELECT conversationID, joinedDate, leftDate, bareJID, jidResource, nickname
FROM entConParticipant;
TRUNCATE TABLE ofMessageArchive;
INSERT INTO ofMessageArchive
(conversationID, fromJID, toJID, sentDate, body)
SELECT conversationID, fromJID, toJID, sentDate, body
FROM entMessageArchive;
TRUNCATE TABLE ofRRDs;
INSERT INTO ofRRDs
(id, updatedDate, bytes)
SELECT id, updatedDate, bytes
FROM entRRDs;
TRUNCATE TABLE ofConversation;
INSERT INTO ofConversation
(conversationID, room, isExternal, startDate, lastActivity, messageCount)
SELECT conversationID, room, isExternal, startDate, lastActivity, messageCount
FROM entConversation;
TRUNCATE TABLE ofConParticipant;
INSERT INTO ofConParticipant
(conversationID, joinedDate, leftDate, bareJID, jidResource, nickname)
SELECT conversationID, joinedDate, leftDate, bareJID, jidResource, nickname
FROM entConParticipant;
TRUNCATE TABLE ofMessageArchive;
INSERT INTO ofMessageArchive
(conversationID, fromJID, toJID, sentDate, body)
SELECT conversationID, fromJID, toJID, sentDate, body
FROM entMessageArchive;
TRUNCATE TABLE ofRRDs;
INSERT INTO ofRRDs
(id, updatedDate, bytes)
SELECT id, updatedDate, bytes
FROM entRRDs;
TRUNCATE TABLE ofConversation;
INSERT INTO ofConversation
(conversationID, room, isExternal, startDate, lastActivity, messageCount)
SELECT conversationID, room, isExternal, startDate, lastActivity, messageCount
FROM entConversation;
TRUNCATE TABLE ofConParticipant;
INSERT INTO ofConParticipant
(conversationID, joinedDate, leftDate, bareJID, jidResource, nickname)
SELECT conversationID, joinedDate, leftDate, bareJID, jidResource, nickname
FROM entConParticipant;
TRUNCATE TABLE ofMessageArchive;
INSERT INTO ofMessageArchive
(conversationID, fromJID, toJID, sentDate, body)
SELECT conversationID, fromJID, toJID, sentDate, body
FROM entMessageArchive;
TRUNCATE TABLE ofRRDs;
INSERT INTO ofRRDs
(id, updatedDate, bytes)
SELECT id, updatedDate, bytes
FROM entRRDs;
commit;
TRUNCATE TABLE ofConversation;
INSERT INTO ofConversation
(conversationID, room, isExternal, startDate, lastActivity, messageCount)
SELECT conversationID, room, isExternal, startDate, lastActivity, messageCount
FROM entConversation;
TRUNCATE TABLE ofConParticipant;
INSERT INTO ofConParticipant
(conversationID, joinedDate, leftDate, bareJID, jidResource, nickname)
SELECT conversationID, joinedDate, leftDate, bareJID, jidResource, nickname
FROM entConParticipant;
TRUNCATE TABLE ofMessageArchive;
INSERT INTO ofMessageArchive
(conversationID, fromJID, toJID, sentDate, body)
SELECT conversationID, fromJID, toJID, sentDate, body
FROM entMessageArchive;
TRUNCATE TABLE ofRRDs;
INSERT INTO ofRRDs
(id, updatedDate, bytes)
SELECT id, updatedDate, bytes
FROM entRRDs;
TRUNCATE TABLE ofConversation;
INSERT INTO ofConversation
(conversationID, room, isExternal, startDate, lastActivity, messageCount)
SELECT conversationID, room, isExternal, startDate, lastActivity, messageCount
FROM entConversation;
TRUNCATE TABLE ofConParticipant;
INSERT INTO ofConParticipant
(conversationID, joinedDate, leftDate, bareJID, jidResource, nickname)
SELECT conversationID, joinedDate, leftDate, bareJID, jidResource, nickname
FROM entConParticipant;
TRUNCATE TABLE ofMessageArchive;
INSERT INTO ofMessageArchive
(conversationID, fromJID, toJID, sentDate, body)
SELECT conversationID, fromJID, toJID, sentDate, body
FROM entMessageArchive;
TRUNCATE TABLE ofRRDs;
INSERT INTO ofRRDs
(id, updatedDate, bytes)
SELECT id, updatedDate, bytes
FROM entRRDs;
-- $Revision$
-- $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
CREATE TABLE ofConversation (
conversationID INTEGER NOT NULL,
room VARCHAR(512),
isExternal INTEGER NOT NULL,
startDate BIGINT NOT NULL,
lastActivity BIGINT NOT NULL,
messageCount INTEGER NOT NULL,
CONSTRAINT ofConversation_pk PRIMARY KEY (conversationID)
);
CREATE INDEX ofConversation_ext_idx ON ofConversation (isExternal);
CREATE INDEX ofConversation_start_idx ON ofConversation (startDate);
CREATE INDEX ofConversation_last_idx ON ofConversation (lastActivity);
CREATE TABLE ofConParticipant (
conversationID INTEGER NOT NULL,
joinedDate BIGINT NOT NULL,
leftDate BIGINT,
bareJID VARCHAR(255) NOT NULL,
jidResource VARCHAR(255) NOT NULL,
nickname VARCHAR(255)
);
CREATE INDEX entConPar_con_idx ON ofConParticipant (conversationID, bareJID, jidResource, joinedDate);
CREATE INDEX entConPar_jid_idx ON ofConParticipant (bareJID);
CREATE TABLE ofMessageArchive (
conversationID INTEGER NOT NULL,
fromJID VARCHAR(1024) NOT NULL,
toJID VARCHAR(1024) NOT NULL,
sentDate BIGINT NOT NULL,
body LONG VARCHAR
);
CREATE INDEX ofMessageArchive_con_idx ON ofMessageArchive (conversationID);
CREATE TABLE ofRRDs (
id VARCHAR(100) NOT NULL,
updatedDate BIGINT NOT NULL,
bytes BLOB,
CONSTRAINT ofRRDs_pk PRIMARY KEY (id)
);
// $Revision$
// $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
CREATE TABLE ofConversation (
conversationID BIGINT NOT NULL,
room VARCHAR(1024) NULL,
isExternal INT NOT NULL,
startDate BIGINT NOT NULL,
lastActivity BIGINT NOT NULL,
messageCount INT NOT NULL,
CONSTRAINT ofConversation_pk PRIMARY KEY (conversationID)
);
CREATE INDEX ofConversation_ext_idx ON ofConversation (isExternal);
CREATE INDEX ofConversation_start_idx ON ofConversation (startDate);
CREATE INDEX ofConversation_last_idx ON ofConversation (lastActivity);
CREATE TABLE ofConParticipant (
conversationID BIGINT NOT NULL,
joinedDate BIGINT NOT NULL,
leftDate BIGINT NULL,
bareJID VARCHAR(255) NOT NULL,
jidResource VARCHAR(255) NULL,
nickname VARCHAR(255) NULL
);
CREATE INDEX ofConParticipant_conv_idx ON ofConParticipant (conversationID, bareJID, jidResource, joinedDate);
CREATE INDEX ofConParticipant_jid_idx ON ofConParticipant (bareJID);
CREATE TABLE ofMessageArchive (
conversationID BIGINT NOT NULL,
fromJID VARCHAR(1024) NOT NULL,
fromJIDResource VARCHAR(255) NULL,
toJID VARCHAR(1024) NOT NULL,
toJIDResource VARCHAR(255) NULL,
sentDate BIGINT NOT NULL,
body LONGVARCHAR
);
CREATE INDEX ofMessageArchive_con_idx ON ofMessageArchive (conversationID);
CREATE TABLE ofRRDs (
id VARCHAR(100) NOT NULL,
updatedDate BIGINT NOT NULL,
bytes VARBINARY NULL,
CONSTRAINT ofRRDs_pk PRIMARY KEY (id)
);
# $Revision$
# $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
CREATE TABLE ofConversation (
conversationID BIGINT NOT NULL,
room VARCHAR(255) NULL,
isExternal TINYINT NOT NULL,
startDate BIGINT NOT NULL,
lastActivity BIGINT NOT NULL,
messageCount INT NOT NULL,
PRIMARY KEY (conversationID),
INDEX ofConversation_ext_idx (isExternal),
INDEX ofConversation_start_idx (startDate),
INDEX ofConversation_last_idx (lastActivity)
);
CREATE TABLE ofConParticipant (
conversationID BIGINT NOT NULL,
joinedDate BIGINT NOT NULL,
leftDate BIGINT NULL,
bareJID VARCHAR(200) NOT NULL,
jidResource VARCHAR(100) NULL,
nickname VARCHAR(255) NULL,
INDEX ofConParticipant_conv_idx (conversationID, bareJID, jidResource, joinedDate),
INDEX ofConParticipant_jid_idx (bareJID)
);
CREATE TABLE ofMessageArchive (
conversationID BIGINT NOT NULL,
fromJID VARCHAR(255) NOT NULL,
fromJIDResource VARCHAR(100) NULL,
toJID VARCHAR(255) NOT NULL,
toJIDResource VARCHAR(100) NULL,
sentDate BIGINT NOT NULL,
body TEXT,
INDEX gtmsMsgArchive_con_idx (conversationID)
);
CREATE TABLE ofRRDs (
id VARCHAR(100) NOT NULL,
updatedDate BIGINT NOT NULL,
bytes MEDIUMBLOB NULL,
PRIMARY KEY (id)
);
-- $Revision$
-- $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
CREATE TABLE ofConversation (
conversationID INTEGER NOT NULL,
room VARCHAR2(1024) NULL,
isExternal NUMBER(2) NOT NULL,
startDate INTEGER NOT NULL,
lastActivity INTEGER NOT NULL,
messageCount INT NOT NULL,
CONSTRAINT ofConversation_pk PRIMARY KEY (conversationID)
);
CREATE INDEX ofConversation_ext_idx ON ofConversation (isExternal);
CREATE INDEX ofConversation_start_idx ON ofConversation (startDate);
CREATE INDEX ofConversation_last_idx ON ofConversation (lastActivity);
CREATE TABLE ofConParticipant (
conversationID INTEGER NOT NULL,
joinedDate INTEGER NOT NULL,
leftDate INTEGER NULL,
bareJID VARCHAR2(255) NOT NULL,
jidResource VARCHAR2(255) NOT NULL,
nickname VARCHAR2(255) NULL
);
CREATE INDEX ofConParticipant_conv_idx ON ofConParticipant (conversationID, bareJID, jidResource, joinedDate);
CREATE INDEX ofConParticipant_jid_idx ON ofConParticipant (bareJID);
CREATE TABLE ofMessageArchive (
conversationID INTEGER NOT NULL,
fromJID VARCHAR2(1024) NOT NULL,
toJID VARCHAR2(1024) NOT NULL,
sentDate INTEGER NOT NULL,
body LONG
);
CREATE INDEX ofMessageArchive_con_idx ON ofMessageArchive (conversationID);
CREATE TABLE ofRRDs (
id VARCHAR2(100) NOT NULL,
updatedDate INTEGER NOT NULL,
bytes BLOB NULL,
CONSTRAINT ofRRDs_pk PRIMARY KEY (id)
);
-- $Revision$
-- $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
CREATE TABLE ofConversation (
conversationID INTEGER NOT NULL,
room VARCHAR(1024) NULL,
isExternal SMALLINT NOT NULL,
startDate BIGINT NOT NULL,
lastActivity BIGINT NOT NULL,
messageCount INTEGER NOT NULL,
CONSTRAINT ofConversation_pk PRIMARY KEY (conversationID)
);
CREATE INDEX ofConversation_ext_idx ON ofConversation (isExternal);
CREATE INDEX ofConversation_start_idx ON ofConversation (startDate);
CREATE INDEX ofConversation_last_idx ON ofConversation (lastActivity);
CREATE TABLE ofConParticipant (
conversationID INTEGER NOT NULL,
joinedDate BIGINT NOT NULL,
leftDate BIGINT NULL,
bareJID VARCHAR(255) NOT NULL,
jidResource VARCHAR(255) NULL,
nickname VARCHAR(255) NULL
);
CREATE INDEX ofConParticipant_conv_idx ON ofConParticipant (conversationID, bareJID, jidResource, joinedDate);
CREATE INDEX ofConParticipant_jid_idx ON ofConParticipant (bareJID);
CREATE TABLE ofMessageArchive (
conversationID INTEGER NOT NULL,
fromJID VARCHAR(1024) NOT NULL,
fromJIDResource VARCHAR(1024) NULL,
toJID VARCHAR(1024) NOT NULL,
toJIDResource VARCHAR(1024) NULL,
sentDate BIGINT NOT NULL,
body TEXT
);
CREATE INDEX ofMessageArchive_con_idx ON ofMessageArchive (conversationID);
CREATE TABLE ofRRDs (
id VARCHAR(100) NOT NULL,
updatedDate BIGINT NOT NULL,
bytes bytea NULL,
CONSTRAINT ofRRDs_pk PRIMARY KEY (id)
);
/* $Revision$ */
/* $Date$ */
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
CREATE TABLE ofConversation (
conversationID BIGINT NOT NULL,
room NVARCHAR(1024) NULL,
isExternal TINYINT NOT NULL,
startDate BIGINT NOT NULL,
lastActivity BIGINT NOT NULL,
messageCount INT NOT NULL,
CONSTRAINT ofConversation_pk PRIMARY KEY (conversationID)
);
CREATE INDEX ofConversation_ext_idx ON ofConversation (isExternal);
CREATE INDEX ofConversation_start_idx ON ofConversation (startDate);
CREATE INDEX ofConversation_last_idx ON ofConversation (lastActivity);
CREATE TABLE ofConParticipant (
conversationID BIGINT NOT NULL,
joinedDate BIGINT NOT NULL,
leftDate BIGINT NULL,
bareJID NVARCHAR(255) NOT NULL,
jidResource NVARCHAR(255) NULL,
nickname NVARCHAR(255) NULL
);
CREATE INDEX ofConParticipant_conv_idx ON ofConParticipant (conversationID, bareJID, jidResource, joinedDate);
CREATE INDEX ofConParticipant_jid_idx ON ofConParticipant (bareJID);
CREATE TABLE ofMessageArchive (
conversationID BIGINT NOT NULL,
fromJID NVARCHAR(1024) NOT NULL,
fromJIDResource NVARCHAR(1024) NULL,
toJID NVARCHAR(1024) NOT NULL,
toJIDResource NVARCHAR(1024) NULL,
sentDate BIGINT NOT NULL,
body NTEXT
);
CREATE INDEX ofMessageArchive_con_idx ON ofMessageArchive (conversationID);
CREATE TABLE ofRRDs (
id NVARCHAR(100) NOT NULL,
updatedDate BIGINT NOT NULL,
bytes IMAGE NULL,
CONSTRAINT ofRRDs_pk PRIMARY KEY (id)
);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package com.reucon.openfire.plugin.archive;
import java.util.Date;
import org.jivesoftware.openfire.session.Session;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
/**
* Factory to create model objects.
*/
public class ArchiveFactory {
private ArchiveFactory() {
}
public static ArchivedMessage createArchivedMessage(Session session,
Message message, ArchivedMessage.Direction direction, JID withJid) {
final ArchivedMessage archivedMessage;
archivedMessage = new ArchivedMessage(new Date(), direction, message
.getType().toString(), withJid);
archivedMessage.setSubject(message.getSubject());
archivedMessage.setBody(message.getBody());
return archivedMessage;
}
}
package com.reucon.openfire.plugin.archive;
import org.jivesoftware.openfire.session.Session;
import org.xmpp.packet.Message;
/**
* Adds messages to the archive.
*/
public interface ArchiveManager
{
/**
* Adds a message to the archive.
*
* @param session the session the message was received through.
* @param message the message to archive.
* @param incoming <code>true</code> if this a message received by the server, <code>false</code> if it
* is sent by the server.
*/
void archiveMessage(Session session, Message message, boolean incoming);
/**
* Sets the conversation timeout.<p>
* A new conversation is created if there no messages have been exchanged between two JIDs
* for the given timeout.
*
* @param conversationTimeout the conversation timeout to set in minutes.
*/
void setConversationTimeout(int conversationTimeout);
}
package com.reucon.openfire.plugin.archive;
/**
* Literals for configuration properties.
*/
public interface ArchiveProperties
{
String ENABLED = "archive.enabled";
String INDEX_DIR = "archive.indexdir";
String CONVERSATION_TIMEOUT = "archive.conversationtimeout";
}
package com.reucon.openfire.plugin.archive;
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
/**
* Consumes an ArchivedMessage.
*/
public interface ArchivedMessageConsumer
{
boolean consume(ArchivedMessage message);
}
package com.reucon.openfire.plugin.archive;
import com.reucon.openfire.plugin.archive.model.Conversation;
import java.util.Collection;
import java.util.Date;
/**
* Maintains an index for message retrieval.
*/
public interface IndexManager
{
/**
* Asynchronously indexes the given object.
* @param object the object to index.
* @return <code>true</code> if successfully queued for indexing, <code>false</code> otherwise.
*/
boolean indexObject(Object object);
/**
* Rebuilds the index.
*
* @return the number of messages indexed or -1 on error.
*/
int rebuildIndex();
Collection<String> searchParticipant(String token);
Collection<Conversation> findConversations(String[] participants, Date startDate, Date endDate, String keywords);
void destroy();
}
package com.reucon.openfire.plugin.archive;
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
import com.reucon.openfire.plugin.archive.model.Conversation;
import com.reucon.openfire.plugin.archive.model.Participant;
import com.reucon.openfire.plugin.archive.xep0059.XmppResultSet;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* Manages database persistence.
*/
public interface PersistenceManager
{
/**
* Creates a new archived message.
*
* @param message the message to create.
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
boolean createMessage(ArchivedMessage message);
/**
* Selects all messages and passes each message to the given callback for processing.
*
* @param callback callback to process messages.
* @return number of messages processed.
*/
int processAllMessages(ArchivedMessageConsumer callback);
/**
* Creates a new conversation.
*
* @param conversation the conversation to create.
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
boolean createConversation(Conversation conversation);
/**
* Updates the end time of a conversation. The conversation must be persisted.
*
* @param conversation conversation to update with id and endDate attributes not null.
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
boolean updateConversationEnd(Conversation conversation);
/**
* Adds a new participant to a conversation.
*
* @param participant the participant to add.
* @param conversationId id of the conversation to add the participant to.
* @return <code>true</code> on success, <code>false</code> otherwise.
*/
boolean createParticipant(Participant participant, Long conversationId);
List<Conversation> findConversations(String[] participants, Date startDate, Date endDate);
/**
* Searches for conversations.
*
* @param startDate earliest start date of the conversation to find or <code>null</code> for any.
* @param endDate latest end date of the conversation to find or <code>null</code> for any.
* @param owner bare jid of the owner of the conversation to find or <code>null</code> for any.
* @param with bare jid of the communication partner or <code>null</code> for any. This is either
* the jid of another XMPP user or the jid of a group chat.
* @return the conversations that matched search critera without messages and participants.
*/
Collection<Conversation> findConversations(Date startDate, Date endDate, String owner, String with, XmppResultSet xmppResultSet);
Collection<Conversation> getActiveConversations(int conversationTimeout);
List<Conversation> getConversations(Collection<Long> conversationIds);
/**
* Returns the conversation with the given owner, with and start time including participants and messages.
*
* @param ownerJid bare jid of the conversation's owner.
* @param withJid bare jid of the communication partner.
* @param start exact start time
* @return the matching conversation or <code>null</code> if none matches.
*/
Conversation getConversation(String ownerJid, String withJid, Date start);
/**
* Returns the conversation with the given id including participants and messages.
*
* @param conversationId id of the conversation to retrieve.
* @return the matching conversation or <code>null</code> if none matches.
*/
Conversation getConversation(Long conversationId);
}
package com.reucon.openfire.plugin.archive.impl;
import com.reucon.openfire.plugin.archive.ArchiveFactory;
import com.reucon.openfire.plugin.archive.ArchiveManager;
import com.reucon.openfire.plugin.archive.IndexManager;
import com.reucon.openfire.plugin.archive.PersistenceManager;
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
import com.reucon.openfire.plugin.archive.model.Conversation;
import com.reucon.openfire.plugin.archive.model.Participant;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.session.Session;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import java.util.ArrayList;
import java.util.Collection;
/**
* Default implementation of ArchiveManager.
*/
public class ArchiveManagerImpl implements ArchiveManager
{
private final PersistenceManager persistenceManager;
private final IndexManager indexManager;
private final Collection<Conversation> activeConversations;
private int conversationTimeout;
public ArchiveManagerImpl(PersistenceManager persistenceManager, IndexManager indexManager,
int conversationTimeout)
{
this.persistenceManager = persistenceManager;
this.indexManager = indexManager;
this.conversationTimeout = conversationTimeout;
activeConversations = persistenceManager.getActiveConversations(conversationTimeout);
}
public void archiveMessage(Session session, Message message, boolean incoming)
{
final XMPPServer server = XMPPServer.getInstance();
final ArchivedMessage.Direction direction;
final ArchivedMessage archivedMessage;
final Conversation conversation;
final JID ownerJid;
final JID withJid;
// TODO support groupchat
if (message.getType() != Message.Type.chat && message.getType() != Message.Type.normal)
{
return;
}
if (server.isLocal(message.getFrom()) && incoming)
{
ownerJid = message.getFrom();
withJid = message.getTo();
// sent by the owner => to
direction = ArchivedMessage.Direction.to;
}
else if (server.isLocal(message.getTo()) && ! incoming)
{
ownerJid = message.getTo();
withJid = message.getFrom();
// received by the owner => from
direction = ArchivedMessage.Direction.from;
}
else
{
return;
}
archivedMessage = ArchiveFactory.createArchivedMessage(session, message, direction, withJid);
if (archivedMessage.isEmpty())
{
return;
}
conversation = determineConversation(ownerJid, withJid, message.getSubject(), message.getThread(), archivedMessage);
archivedMessage.setConversation(conversation);
persistenceManager.createMessage(archivedMessage);
if (indexManager != null)
{
indexManager.indexObject(archivedMessage);
}
}
public void setConversationTimeout(int conversationTimeout)
{
this.conversationTimeout = conversationTimeout;
}
private Conversation determineConversation(JID ownerJid, JID withJid, String subject, String thread, ArchivedMessage archivedMessage)
{
Conversation conversation = null;
Collection<Conversation> staleConversations;
staleConversations = new ArrayList<Conversation>();
synchronized (activeConversations)
{
for (Conversation c : activeConversations)
{
if (c.isStale(conversationTimeout))
{
staleConversations.add(c);
continue;
}
if (matches(ownerJid, withJid, thread, c))
{
conversation = c;
break;
}
}
activeConversations.removeAll(staleConversations);
if (conversation == null)
{
final Participant p1;
final Participant p2;
conversation = new Conversation(archivedMessage.getTime(),
ownerJid.toBareJID(), ownerJid.getResource(), withJid.toBareJID(), withJid.getResource(),
subject, thread);
persistenceManager.createConversation(conversation);
p1 = new Participant(archivedMessage.getTime(), ownerJid.toBareJID());
conversation.addParticipant(p1);
persistenceManager.createParticipant(p1, conversation.getId());
p2 = new Participant(archivedMessage.getTime(), withJid.toBareJID());
conversation.addParticipant(p2);
persistenceManager.createParticipant(p2, conversation.getId());
activeConversations.add(conversation);
}
else
{
conversation.setEnd(archivedMessage.getTime());
persistenceManager.updateConversationEnd(conversation);
}
}
return conversation;
}
private boolean matches(JID ownerJid, JID withJid, String thread, Conversation c)
{
if (! ownerJid.toBareJID().equals(c.getOwnerJid()))
{
return false;
}
if (! withJid.toBareJID().equals(c.getWithJid()))
{
return false;
}
/*
if (ownerJid.getResource() != null)
{
if (! ownerJid.getResource().equals(c.getOwnerResource()))
{
return false;
}
}
else
{
if (c.getOwnerResource() != null)
{
return false;
}
}
if (withJid.getResource() != null)
{
if (! withJid.getResource().equals(c.getWithResource()))
{
return false;
}
}
else
{
if (c.getWithResource() != null)
{
return false;
}
}
*/
if (thread != null)
{
if (! thread.equals(c.getThread()))
{
return false;
}
}
else
{
if (c.getThread() != null)
{
return false;
}
}
return true;
}
}
package com.reucon.openfire.plugin.archive.model;
import org.jivesoftware.database.JiveID;
import org.xmpp.packet.JID;
import java.util.Date;
/**
* An archived message.
*/
@JiveID(601)
public class ArchivedMessage {
public enum Direction {
/**
* A message sent by the owner.
*/
to,
/**
* A message received by the owner.
*/
from
}
private Long id;
private final Date time;
private final Direction direction;
private final String type;
private String subject;
private String body;
private Conversation conversation;
private JID withJid;
public ArchivedMessage(Date time, Direction direction, String type, JID withJid) {
this.time = time;
this.direction = direction;
this.type = type;
this.withJid = withJid;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getTime() {
return time;
}
public Direction getDirection() {
return direction;
}
public String getType() {
return type;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Conversation getConversation() {
return conversation;
}
public void setConversation(Conversation conversation) {
this.conversation = conversation;
}
/**
* Checks if this message contains payload that should be archived.
*
* @return <code>true</code> if this message is empty, <code>false</code>
* otherwise.
*/
public boolean isEmpty() {
return subject == null && body == null;
}
public JID getWithJid() {
return withJid;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ArchivedMessage[id=").append(id).append(",");
sb.append("time=").append(time).append(",");
sb.append("direction=").append(direction).append("]");
return sb.toString();
}
}
package com.reucon.openfire.plugin.archive.model;
import org.jivesoftware.database.JiveID;
import java.util.*;
/**
* A conversation between two or more participants.
*/
@JiveID(602)
public class Conversation
{
private Long id;
private final Date start;
private Date end;
private final String ownerJid;
private final String ownerResource;
private final String withJid;
private final String withResource;
private String subject;
private final String thread;
private final List<Participant> participants;
private final List<ArchivedMessage> messages;
public Conversation(Date start, String ownerJid, String ownerResource, String withJid, String withResource,
String subject, String thread)
{
this(start, start, ownerJid, ownerResource, withJid, withResource, subject, thread);
}
public Conversation(Date start, Date end, String ownerJid, String ownerResource, String withJid, String withResource,
String subject, String thread)
{
this.start = start;
this.end = end;
this.ownerJid = ownerJid;
this.ownerResource = ownerResource;
this.withJid = withJid;
this.withResource = withResource;
this.subject = subject;
this.thread = thread;
participants = new ArrayList<Participant>();
messages = new ArrayList<ArchivedMessage>();
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public Date getStart()
{
return start;
}
public Date getEnd()
{
return end;
}
public void setEnd(Date end)
{
this.end = end;
}
public String getOwnerJid()
{
return ownerJid;
}
public String getOwnerResource()
{
return ownerResource;
}
public String getWithJid()
{
return withJid;
}
public String getWithResource()
{
return withResource;
}
public String getSubject()
{
return subject;
}
public void setSubject(String subject)
{
this.subject = subject;
}
public String getThread()
{
return thread;
}
public Collection<Participant> getParticipants()
{
return Collections.unmodifiableCollection(participants);
}
public void addParticipant(Participant participant)
{
synchronized (participants)
{
participants.add(participant);
}
}
public List<ArchivedMessage> getMessages()
{
return Collections.unmodifiableList(messages);
}
public void addMessage(ArchivedMessage message)
{
synchronized (messages)
{
messages.add(message);
}
}
public boolean isStale(int conversationTimeout)
{
Long now = System.currentTimeMillis();
return end.getTime() + conversationTimeout * 60L * 1000L < now;
}
/**
* Checks if this conversation has an active participant with the given JID.
*
* @param jid JID of the participant
* @return <code>true</code> if this conversation has an active participant with the given JID,
* <code>false</code> otherwise.
*/
public boolean hasParticipant(String jid)
{
synchronized (participants)
{
for (Participant p : participants)
{
if (p.getJid().equals(jid))
{
return true;
}
}
}
return false;
}
/**
* Checks if this conversation is new and has not yet been persisted.
*
* @return <code>true</code> if this conversation is new and has not yet been persisted,
* <code>false</code> otherwise.
*/
public boolean isNew()
{
return id == null;
}
}
package com.reucon.openfire.plugin.archive.model;
import org.jivesoftware.database.JiveID;
import java.util.Date;
/**
*
*/
@JiveID(603)
public class Participant
{
private long id;
private final Date start;
private Date end;
private final String jid;
public Participant(Date start, String jid)
{
this.start = start;
this.jid = jid;
}
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public Date getStart()
{
return start;
}
public Date getEnd()
{
return end;
}
public void setEnd(Date end)
{
this.end = end;
}
public String getJid()
{
return jid;
}
}
package com.reucon.openfire.plugin.archive.model;
import java.util.Map;
/**
* A user's archiving preferences according to XEP-0136.
*/
public class Preferences
{
public enum MethodUsage
{
forbid,
concide,
prefer
}
private String username;
private Map<String, MethodUsage> methods;
}
package com.reucon.openfire.plugin.archive.util;
public class EscapeUtil
{
public static String escapeHtml(String source)
{
int terminatorIndex;
if (source == null)
{
return null;
}
StringBuffer result = new StringBuffer(source.length() * 2);
for (int i = 0; i < source.length(); i++)
{
int ch = source.charAt(i);
// avoid escaping already escaped characters
if (ch == 38)
{
terminatorIndex = source.indexOf(";", i);
if (terminatorIndex > 0)
{
if (source.substring(i + 1, terminatorIndex).matches("#[0-9]+|lt|gt|amp|quote"))
{
result.append(source.substring(i, terminatorIndex + 1));
// Skip remaining chars up to (and including) ";"
i = terminatorIndex;
continue;
}
}
}
if (ch == 10)
{
result.append("<br/>");
}
else if (ch != 32 && (ch > 122 || ch < 48 || ch == 60 || ch == 62))
{
result.append("&#");
result.append(ch);
result.append(";");
}
else
{
result.append((char) ch);
}
}
return new String(result);
}
}
package com.reucon.openfire.plugin.archive.util;
import org.jivesoftware.util.JiveConstants;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* Utility class to parse and format dates in UTC that adhere to the DateTime format specified
* in Jabber Date and Time Profiles.
*/
public class XmppDateUtil
{
private static final DateFormat dateFormat;
private static final DateFormat dateFormatWithoutMillis;
static
{
dateFormat = new SimpleDateFormat(JiveConstants.XMPP_DATETIME_FORMAT);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
dateFormatWithoutMillis = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormatWithoutMillis.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private XmppDateUtil()
{
}
public static Date parseDate(String dateString)
{
Date date = null;
if (dateString == null)
{
return null;
}
synchronized(dateFormat)
{
try
{
date = dateFormat.parse(dateString);
}
catch (ParseException e)
{
// ignore
}
}
if (date != null)
{
return date;
}
synchronized(dateFormatWithoutMillis)
{
try
{
date = dateFormatWithoutMillis.parse(dateString);
}
catch (ParseException e)
{
// ignore
}
}
return date;
}
public static String formatDate(Date date)
{
if (date == null)
{
return null;
}
synchronized(dateFormat)
{
return dateFormat.format(date);
}
}
}
package com.reucon.openfire.plugin.archive.xep0059;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
/**
* A <a href="http://www.xmpp.org/extensions/xep-0059.html">XEP-0059</a> result set.
*/
public class XmppResultSet
{
public static String NAMESPACE = "http://jabber.org/protocol/rsm";
private Long after;
private Long before;
private Integer index;
private Integer max;
private Long first;
private Integer firstIndex;
private Long last;
private Integer count;
public XmppResultSet(Element setElement)
{
if (setElement.element("after") != null)
{
try
{
after = Long.parseLong(setElement.elementText("after"));
if (after < 0)
{
after = null;
}
}
catch (Exception e)
{
// swallow
}
}
if (setElement.element("before") != null)
{
try
{
before = Long.parseLong(setElement.elementText("before"));
if (before < 0)
{
before = null;
}
}
catch (Exception e)
{
// swallow
}
}
if (setElement.element("max") != null)
{
try
{
max = Integer.parseInt(setElement.elementText("max"));
if (max < 0)
{
max = null;
}
}
catch (Exception e)
{
// swallow
}
}
if (setElement.element("index") != null)
{
try
{
index = Integer.parseInt(setElement.elementText("index"));
if (index < 0)
{
index = null;
}
}
catch (Exception e)
{
// swallow
}
}
}
public Long getAfter()
{
return after;
}
public Long getBefore()
{
return before;
}
/**
* Returns the index of the first element to return.
*
* @return the index of the first element to return.
*/
public Integer getIndex()
{
return index;
}
/**
* Returns the maximum number of items to return.
*
* @return the maximum number of items to return.
*/
public Integer getMax()
{
return max;
}
/**
* Sets the id of the first element returned.
*
* @param first the id of the first element returned.
*/
public void setFirst(Long first)
{
this.first = first;
}
/**
* Sets the index of the first element returned.
*
* @param firstIndex the index of the first element returned.
*/
public void setFirstIndex(Integer firstIndex)
{
this.firstIndex = firstIndex;
}
/**
* Sets the id of the last element returned.
*
* @param last the id of the last element returned.
*/
public void setLast(Long last)
{
this.last = last;
}
/**
* Sets the number of elements returned.
*
* @param count the number of elements returned.
*/
public void setCount(Integer count)
{
this.count = count;
}
public Element createResultElement()
{
final Element set;
set = DocumentFactory.getInstance().createElement("set", NAMESPACE);
if (first != null)
{
final Element firstElement;
firstElement = set.addElement("first");
firstElement.setText(first.toString());
if (firstIndex != null)
{
firstElement.addAttribute("index", firstIndex.toString());
}
}
if (last != null)
{
set.addElement("last").setText(last.toString());
}
if (count != null)
{
set.addElement("count").setText(count.toString());
}
return set;
}
}
package com.reucon.openfire.plugin.archive.xep0136;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.plugin.MonitoringPlugin;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import com.reucon.openfire.plugin.archive.IndexManager;
import com.reucon.openfire.plugin.archive.PersistenceManager;
/**
* Abstract base class for XEP-0136 IQ Handlers.
*/
public abstract class AbstractIQHandler extends IQHandler {
protected static final String NAMESPACE = "urn:xmpp:archive";
private final IQHandlerInfo info;
protected AbstractIQHandler(String moduleName, String elementName) {
super(moduleName);
this.info = new IQHandlerInfo(elementName, NAMESPACE);
}
public final IQHandlerInfo getInfo() {
return info;
}
protected PersistenceManager getPersistenceManager() {
return MonitoringPlugin.getInstance().getPersistenceManager();
}
protected IndexManager getIndexManager() {
return MonitoringPlugin.getInstance().getIndexManager();
}
protected IQ error(Packet packet, PacketError.Condition condition) {
IQ reply;
reply = new IQ(IQ.Type.error, packet.getID());
reply.setFrom(packet.getTo());
reply.setTo(packet.getFrom());
reply.setError(condition);
return reply;
}
}
package com.reucon.openfire.plugin.archive.xep0136;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.dom4j.Element;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import com.reucon.openfire.plugin.archive.model.Conversation;
import com.reucon.openfire.plugin.archive.util.XmppDateUtil;
import com.reucon.openfire.plugin.archive.xep0059.XmppResultSet;
/**
* Message Archiving List Handler.
*/
public class IQListHandler extends AbstractIQHandler implements
ServerFeaturesProvider {
private static final String NAMESPACE_MANAGE = "urn:xmpp:archive:manage";
public IQListHandler() {
super("Message Archiving List Handler", "list");
}
public IQ handleIQ(IQ packet) throws UnauthorizedException {
IQ reply = IQ.createResultIQ(packet);
ListRequest listRequest = new ListRequest(packet.getChildElement());
JID from = packet.getFrom();
Element listElement = reply.setChildElement("list", NAMESPACE);
Collection<Conversation> conversations = list(from, listRequest);
XmppResultSet resultSet = listRequest.getResultSet();
for (Conversation conversation : conversations) {
addChatElement(listElement, conversation);
}
if (resultSet != null) {
listElement.add(resultSet.createResultElement());
}
return reply;
}
private Collection<Conversation> list(JID from, ListRequest request) {
return getPersistenceManager().findConversations(request.getStart(),
request.getEnd(), from.toBareJID(), request.getWith(),
request.getResultSet());
}
private Element addChatElement(Element listElement,
Conversation conversation) {
Element chatElement = listElement.addElement("chat");
chatElement.addAttribute("with", conversation.getWithJid());
chatElement.addAttribute("start",
XmppDateUtil.formatDate(conversation.getStart()));
return chatElement;
}
public Iterator<String> getFeatures() {
ArrayList<String> features = new ArrayList<String>();
features.add(NAMESPACE_MANAGE);
return features.iterator();
}
}
package com.reucon.openfire.plugin.archive.xep0136;
import org.dom4j.Element;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.xmpp.packet.IQ;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Message Archiving Preferences Handler.
*/
public class IQPrefHandler extends AbstractIQHandler implements ServerFeaturesProvider
{
private static final String NAMESPACE_PREF = "urn:xmpp:archive:pref";
public IQPrefHandler()
{
super("Message Archiving Preferences Handler", "pref");
}
@SuppressWarnings("unchecked")
public IQ handleIQ(IQ packet) throws UnauthorizedException
{
IQ reply = IQ.createResultIQ(packet);
Element prefRequest = packet.getChildElement();
System.err.println("Received pref message from " + packet.getFrom());
if (prefRequest.element("default") != null)
{
Element defaultItem = prefRequest.element("default");
// User requests to set default modes
defaultItem.attribute("save"); // body, false, message, stream
defaultItem.attribute("otr");
defaultItem.attribute("expire");
}
for (Element item : (List<Element>) prefRequest.elements("item"))
{
// User requests to set modes for a contact
item.attribute("jid");
item.attribute("save"); // body, false, message, stream
item.attribute("otr");
item.attribute("expire");
}
for (Element method : (List<Element>) prefRequest.elements("method"))
{
// User requests to set archiving method preferences
method.attribute("type");
method.attribute("use");
}
return reply;
}
public Iterator<String> getFeatures()
{
ArrayList<String> features = new ArrayList<String>();
features.add(NAMESPACE_PREF);
return features.iterator();
}
}
package com.reucon.openfire.plugin.archive.xep0136;
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
import com.reucon.openfire.plugin.archive.model.Conversation;
import com.reucon.openfire.plugin.archive.util.XmppDateUtil;
import com.reucon.openfire.plugin.archive.xep0059.XmppResultSet;
import org.dom4j.Element;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import java.util.List;
/**
* Message Archiving Retrieve Handler.
*/
public class IQRetrieveHandler extends AbstractIQHandler {
public IQRetrieveHandler() {
super("Message Archiving Retrieve Handler", "retrieve");
}
public IQ handleIQ(IQ packet) throws UnauthorizedException {
final IQ reply = IQ.createResultIQ(packet);
final RetrieveRequest retrieveRequest = new RetrieveRequest(
packet.getChildElement());
int fromIndex; // inclusive
int toIndex; // exclusive
int max;
final Conversation conversation = retrieve(packet.getFrom(),
retrieveRequest);
if (conversation == null) {
return error(packet, PacketError.Condition.item_not_found);
}
final Element chatElement = reply.setChildElement("chat", NAMESPACE);
chatElement.addAttribute("with", conversation.getWithJid());
chatElement.addAttribute("start",
XmppDateUtil.formatDate(conversation.getStart()));
max = conversation.getMessages().size();
fromIndex = 0;
toIndex = max > 0 ? max : 0;
final XmppResultSet resultSet = retrieveRequest.getResultSet();
if (resultSet != null) {
if (resultSet.getMax() != null && resultSet.getMax() <= max) {
max = resultSet.getMax();
toIndex = fromIndex + max;
}
if (resultSet.getIndex() != null) {
fromIndex = resultSet.getIndex();
toIndex = fromIndex + max;
} else if (resultSet.getAfter() != null) {
fromIndex = resultSet.getAfter().intValue() + 1;
toIndex = fromIndex + max;
} else if (resultSet.getBefore() != null) {
toIndex = resultSet.getBefore().intValue();
fromIndex = toIndex - max;
}
}
fromIndex = fromIndex < 0 ? 0 : fromIndex;
toIndex = toIndex > conversation.getMessages().size() ? conversation
.getMessages().size() : toIndex;
toIndex = toIndex < fromIndex ? fromIndex : toIndex;
final List<ArchivedMessage> messages = conversation.getMessages()
.subList(fromIndex, toIndex);
for (ArchivedMessage message : messages) {
addMessageElement(chatElement, conversation, message);
}
if (resultSet != null && messages.size() > 0) {
resultSet.setFirst((long) fromIndex);
resultSet.setFirstIndex(fromIndex);
resultSet.setLast((long) toIndex - 1);
resultSet.setCount(conversation.getMessages().size());
chatElement.add(resultSet.createResultElement());
}
return reply;
}
private Conversation retrieve(JID from, RetrieveRequest request) {
return getPersistenceManager().getConversation(from.toBareJID(),
request.getWith(), request.getStart());
}
private Element addMessageElement(Element parentElement,
Conversation conversation, ArchivedMessage message) {
final Element messageElement;
final long secs;
secs = (message.getTime().getTime() - conversation.getStart().getTime()) / 1000;
messageElement = parentElement.addElement(message.getDirection()
.toString());
messageElement.addAttribute("secs", Long.toString(secs));
if (message.getWithJid() != null) {
messageElement.addAttribute("jid", message.getWithJid().toBareJID());
}
messageElement.addElement("body").setText(message.getBody());
return messageElement;
}
}
package com.reucon.openfire.plugin.archive.xep0136;
import com.reucon.openfire.plugin.archive.util.XmppDateUtil;
import com.reucon.openfire.plugin.archive.xep0059.XmppResultSet;
import org.dom4j.Element;
import org.dom4j.QName;
import java.util.Date;
/**
* A request to retrieve a list of collections.
*/
public class ListRequest
{
private String with;
private Date start;
private Date end;
private XmppResultSet resultSet;
public ListRequest(Element listElement)
{
if (listElement.attribute("with") != null)
{
this.with = listElement.attributeValue("with");
}
if (listElement.attribute("start") != null)
{
this.start = XmppDateUtil.parseDate(listElement.attributeValue("start"));
}
if (listElement.attribute("end") != null)
{
this.end = XmppDateUtil.parseDate(listElement.attributeValue("end"));
}
Element setElement = listElement.element(QName.get("set", XmppResultSet.NAMESPACE));
if (setElement != null)
{
resultSet = new XmppResultSet(setElement);
}
}
public String getWith()
{
return with;
}
public Date getStart()
{
return start;
}
public Date getEnd()
{
return end;
}
public XmppResultSet getResultSet()
{
return resultSet;
}
}
package com.reucon.openfire.plugin.archive.xep0136;
import com.reucon.openfire.plugin.archive.util.XmppDateUtil;
import org.dom4j.Element;
import java.util.Date;
/**
* A request to remove one or more collections.
* <p>
* To request the removal of a single collection the client sends an empty &lt;remove/&gt; element.<br>
* The 'with' (full JID) and 'start' attributes MUST be included to uniquely identify the collection.
* <p>
* The client may remove several collections at once.<br/>
* The 'start' and 'end' elements MAY be specified to indicate a date range.<br/>
* The 'with' attribute MAY be a full JID, bare JID or domain.
* <p>
* If the value of the optional 'open' attribute is set to 'true' then only collections that are currently
* being recorded automatically by the server (see Automated Archiving) are removed.
*/
public class RemoveRequest
{
private final String with;
private final Date start;
private final Date end;
private final boolean open;
public RemoveRequest(Element listElement)
{
this.with = listElement.attributeValue("with");
this.start = XmppDateUtil.parseDate(listElement.attributeValue("start"));
this.end = XmppDateUtil.parseDate(listElement.attributeValue("end"));
this.open = "true".equals(listElement.attributeValue("open"));
}
/**
* The 'with' attribute MAY be a full JID, bare JID or domain.<br>
* If the 'with' attribute is omitted then collections with any JID are removed.
*
* @return the value of the with attribute, may be <code>null</code>.
*/
public String getWith()
{
return with;
}
/**
* If the start date is before all the collections in the archive then all collections prior
* to the end date are removed.
*
* @return the value of the start attribute, may be <code>null</code>.
*/
public Date getStart()
{
return start;
}
/**
* If the end date is in the future then then all collections after the start date are removed.
*
* @return the value of the end attribute, may be <code>null</code>.
*/
public Date getEnd()
{
return end;
}
/**
* If the value of the optional 'open' attribute is set to 'true' then only collections that
* are currently being recorded automatically by the server (see Automated Archiving) are removed.
*
* @return the value of the open attribute or <code>false</code> if not set.
*/
public boolean getOpen()
{
return open;
}
}
\ No newline at end of file
package com.reucon.openfire.plugin.archive.xep0136;
import com.reucon.openfire.plugin.archive.util.XmppDateUtil;
import com.reucon.openfire.plugin.archive.xep0059.XmppResultSet;
import org.dom4j.Element;
import org.dom4j.QName;
import java.util.Date;
/**
* A request to retrieve a collection.
*/
public class RetrieveRequest
{
private String with;
private Date start;
private XmppResultSet resultSet;
public RetrieveRequest(Element listElement)
{
this.with = listElement.attributeValue("with");
this.start = XmppDateUtil.parseDate(listElement.attributeValue("start"));
Element setElement = listElement.element(QName.get("set", XmppResultSet.NAMESPACE));
if (setElement != null)
{
resultSet = new XmppResultSet(setElement);
}
}
public String getWith()
{
return with;
}
public Date getStart()
{
return start;
}
public XmppResultSet getResultSet()
{
return resultSet;
}
}
package com.reucon.openfire.plugin.archive.xep0136;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.jivesoftware.openfire.IQRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.IQDiscoInfoHandler;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.plugin.MonitoringPlugin;
import org.jivesoftware.util.Log;
import org.xmpp.packet.IQ;
import org.xmpp.packet.PacketError;
/**
* Encapsulates support for <a
* href="http://www.xmpp.org/extensions/xep-0136.html">XEP-0136</a>.
*/
public class Xep0136Support {
private static final String NAMESPACE_AUTO = "urn:xmpp:archive:auto";
final XMPPServer server;
final Map<String, IQHandler> element2Handlers;
final IQHandler iqDispatcher;
final Collection<IQHandler> iqHandlers;
public Xep0136Support(XMPPServer server) {
this.server = server;
this.element2Handlers = Collections
.synchronizedMap(new HashMap<String, IQHandler>());
this.iqDispatcher = new AbstractIQHandler("XEP-0136 IQ Dispatcher",
null) {
public IQ handleIQ(IQ packet) throws UnauthorizedException {
if (!MonitoringPlugin.getInstance().isEnabled()) {
return error(packet,
PacketError.Condition.feature_not_implemented);
}
final IQHandler iqHandler = element2Handlers.get(packet
.getChildElement().getName());
if (iqHandler != null) {
return iqHandler.handleIQ(packet);
} else {
return error(packet,
PacketError.Condition.feature_not_implemented);
}
}
};
iqHandlers = new ArrayList<IQHandler>();
// support for #ns-pref
// iqHandlers.add(new IQPrefHandler());
// support for #ns-manage
iqHandlers.add(new IQListHandler());
iqHandlers.add(new IQRetrieveHandler());
// iqHandlers.add(new IQRemoveHandler());
}
public void start() {
for (IQHandler iqHandler : iqHandlers) {
try {
iqHandler.initialize(server);
iqHandler.start();
} catch (Exception e) {
Log.error("Unable to initialize and start "
+ iqHandler.getClass());
continue;
}
element2Handlers.put(iqHandler.getInfo().getName(), iqHandler);
if (iqHandler instanceof ServerFeaturesProvider) {
for (Iterator<String> i = ((ServerFeaturesProvider) iqHandler)
.getFeatures(); i.hasNext();) {
server.getIQDiscoInfoHandler().addServerFeature(i.next());
}
}
}
server.getIQDiscoInfoHandler().addServerFeature(NAMESPACE_AUTO);
server.getIQRouter().addHandler(iqDispatcher);
}
public void stop() {
IQRouter iqRouter = server.getIQRouter();
IQDiscoInfoHandler iqDiscoInfoHandler = server.getIQDiscoInfoHandler();
for (IQHandler iqHandler : iqHandlers) {
element2Handlers.remove(iqHandler.getInfo().getName());
try {
iqHandler.stop();
iqHandler.destroy();
} catch (Exception e) {
Log.warn("Unable to stop and destroy " + iqHandler.getClass());
}
if (iqHandler instanceof ServerFeaturesProvider) {
for (Iterator<String> i = ((ServerFeaturesProvider) iqHandler)
.getFeatures(); i.hasNext();) {
if (iqDiscoInfoHandler != null) {
iqDiscoInfoHandler.removeServerFeature(i.next());
}
}
}
}
if (iqRouter != null) {
iqRouter.removeHandler(iqDispatcher);
}
}
}
/**
* $Revision: 3034 $
* $Date: 2005-11-04 21:02:33 -0300 (Fri, 04 Nov 2005) $
*
* Copyright (C) 2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.archive;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.jivesoftware.openfire.interceptor.PacketInterceptor;
import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.session.Session;
import org.picocontainer.Startable;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import java.util.Date;
/**
* Intercepts packets to track conversations. Only the following messages
* are processed:
* <ul>
* <li>Messages sent between local users.</li>
* <li>Messages sent between local user and remote entities (e.g. remote users).</li>
* <li>Messages sent between local users and users using legacy networks (i.e. transports).</li>
* </ul>
* Therefore, messages that are sent to Publish-Subscribe or any other internal service are ignored.
*
* @author Matt Tucker
*/
public class ArchiveInterceptor implements PacketInterceptor, Startable {
private ConversationManager conversationManager;
public ArchiveInterceptor(ConversationManager conversationManager) {
this.conversationManager = conversationManager;
}
public void interceptPacket(Packet packet, Session session, boolean incoming, boolean processed)
throws PacketRejectedException
{
// Ignore any packets that haven't already been processed by interceptors.
if (!processed) {
return;
}
if (packet instanceof Message) {
// Ignore any outgoing messages (we'll catch them when they're incoming).
if (!incoming) {
return;
}
Message message = (Message) packet;
// Ignore any messages that don't have a body so that we skip events.
// Note: XHTML messages should always include a body so we should be ok. It's
// possible that we may need special XHTML filtering in the future, however.
if (message.getBody() != null) {
// Only process messages that are between two users, group chat rooms, or gateways.
if (conversationManager.isConversation(message)) {
// Process this event in the senior cluster member or local JVM when not in a cluster
if (ClusterManager.isSeniorClusterMember()) {
conversationManager.processMessage(message.getFrom(), message.getTo(), message.getBody(), new Date());
}
else {
JID sender = message.getFrom();
JID receiver = message.getTo();
ConversationEventsQueue eventsQueue = conversationManager.getConversationEventsQueue();
eventsQueue.addChatEvent(conversationManager.getConversationKey(sender, receiver),
ConversationEvent.chatMessageReceived(sender, receiver,
conversationManager.isMessageArchivingEnabled() ? message.getBody() : null,
new Date()));
}
}
}
}
}
public void start() {
InterceptorManager.getInstance().addInterceptor(this);
}
public void stop() {
InterceptorManager.getInstance().removeInterceptor(this);
conversationManager = null;
}
}
/**
* $Revision: 3034 $
* $Date: 2005-11-04 21:02:33 -0300 (Fri, 04 Nov 2005) $
*
* Copyright (C) 2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.archive;
import org.xmpp.packet.JID;
import java.util.Date;
/**
* Represents an archived message.
*
* @author Matt Tucker
*/
public class ArchivedMessage {
private long conversationID;
private JID fromJID;
private JID toJID;
private Date sentDate;
private String body;
private boolean roomEvent;
/**
* Creates a new archived message.
*
* @param conversationID the ID of the conversation that the message is associated with.
* @param fromJID the JID of the user that sent the message.
* @param toJID the JID of the user that the message was sent to.
* @param sentDate the date the message was sent.
* @param body the body of the message
* @param roomEvent true if the message belongs to a room event. Eg. User joined room.
*/
public ArchivedMessage(long conversationID, JID fromJID, JID toJID, Date sentDate, String body, boolean roomEvent) {
this.conversationID = conversationID;
// Convert both JID's to bare JID's so that we don't store resource information.
this.fromJID = fromJID;
this.toJID = toJID;
this.sentDate = sentDate;
this.body = body;
this.roomEvent = roomEvent;
}
/**
* The conversation ID that the message is associated with.
*
* @return the conversation ID.
*/
public long getConversationID() {
return conversationID;
}
/**
* The JID of the user that sent the message.
*
* @return the sender JID.
*/
public JID getFromJID() {
return fromJID;
}
/**
* The JID of the user that received the message.
*
* @return the recipient JID.
*/
public JID getToJID() {
return toJID;
}
/**
* The date the message was sent.
*
* @return the date the message was sent.
*/
public Date getSentDate() {
return sentDate;
}
/**
* The body of the message.
*
* @return the body of the message.
*/
public String getBody() {
return body;
}
/**
* Returns true if the message belongs to a room event. Examples of room events are:
* user joined the room or user left the room.
*
* @return true if the message belongs to a room event.
*/
public boolean isRoomEvent() {
return roomEvent;
}
}
\ No newline at end of file
/**
* $Revision$
* $Date$
*
* Copyright (C) 2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.archive;
import org.jivesoftware.util.StringUtils;
/**
*
*/
public class ConversationInfo {
private long conversationID;
private String participant1;
private String participant2;
/**
* For group converstion we need to send a string array with the occupants' JIDs.
*/
private String[] allParticipants;
private String date;
private String lastActivity;
private String body;
private int messageCount;
private long duration;
public long getConversationID() {
return conversationID;
}
public void setConversationID(long conversationID) {
this.conversationID = conversationID;
}
public String getParticipant1() {
return participant1;
}
public void setParticipant1(String participant1) {
this.participant1 = participant1;
}
public String getParticipant2() {
return participant2;
}
public void setParticipant2(String participant2) {
this.participant2 = participant2;
}
public String[] getAllParticipants() {
return allParticipants;
}
public void setAllParticipants(String[] allParticipants) {
this.allParticipants = allParticipants;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public int getMessageCount() {
return messageCount;
}
public void setMessageCount(int messageCount) {
this.messageCount = messageCount;
}
public String getDuration() {
return StringUtils.getTimeFromLong(duration);
}
public void setDuration(long duration) {
this.duration = duration;
}
public String getLastActivity() {
return lastActivity;
}
public void setLastActivity(String lastActivity) {
this.lastActivity = lastActivity;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment