Commit 19d1b41e authored by Leon Roy's avatar Leon Roy Committed by leonroy

Monitoring plugin 1.3.0 integrating Jive Monitoring Plugin and Stefan Reuter's...

Monitoring plugin 1.3.0 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/trunk@13322 b35dd754-fafc-0310-a699-88a17e54d16e
parent 8a215713
......@@ -5,11 +5,11 @@
<name>Monitoring Service</name>
<description>Monitors conversations and statistics of the server.</description>
<author>Jive Software</author>
<version>1.2.0</version>
<date>12/1/2009</date>
<version>1.3.0</version>
<date>17/10/2012</date>
<minServerVersion>3.7.0</minServerVersion>
<databaseKey>monitoring</databaseKey>
<databaseVersion>0</databaseVersion>
<databaseVersion>1</databaseVersion>
<adminconsole>
<tab id="tab-server">
......
-- $Revision$
-- $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 1);
CREATE TABLE ofConversation (
conversationID INTEGER NOT NULL,
......@@ -30,7 +30,9 @@ CREATE INDEX entConPar_jid_idx ON ofConParticipant (bareJID);
CREATE TABLE ofMessageArchive (
conversationID INTEGER 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 LONG VARCHAR
);
......
// $Revision$
// $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 1);
CREATE TABLE ofConversation (
conversationID BIGINT NOT NULL,
......@@ -30,7 +30,9 @@ 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
);
......@@ -38,7 +40,7 @@ CREATE INDEX ofMessageArchive_con_idx ON ofMessageArchive (conversationID);
CREATE TABLE ofRRDs (
id VARCHAR(100) NOT NULL,
updatedDate BIGINT 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);
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 1);
CREATE TABLE ofConversation (
conversationID BIGINT NOT NULL,
......@@ -30,10 +30,12 @@ CREATE TABLE ofConParticipant (
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 entMsgArchive_con_idx (conversationID)
INDEX gtmsMsgArchive_con_idx (conversationID)
);
CREATE TABLE ofRRDs (
......
-- $Revision$
-- $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 1);
CREATE TABLE ofConversation (
conversationID INTEGER NOT NULL,
......@@ -30,7 +30,9 @@ CREATE INDEX ofConParticipant_jid_idx ON ofConParticipant (bareJID);
CREATE TABLE ofMessageArchive (
conversationID INTEGER NOT NULL,
fromJID VARCHAR2(1024) NOT NULL,
fromJIDResource VARCHAR2(255) NULL,
toJID VARCHAR2(1024) NOT NULL,
toJIDResource VARCHAR2(255) NULL,
sentDate INTEGER NOT NULL,
body LONG
);
......
-- $Revision$
-- $Date$
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 1);
CREATE TABLE ofConversation (
conversationID INTEGER NOT NULL,
......@@ -30,7 +30,9 @@ 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
);
......
/* $Revision$ */
/* $Date$ */
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 0);
INSERT INTO ofVersion (name, version) VALUES ('monitoring', 1);
CREATE TABLE ofConversation (
conversationID BIGINT NOT NULL,
......@@ -30,7 +30,9 @@ 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
);
......
-- $Revision$
-- $Date$
ALTER TABLE ofMessageArchive ADD COLUMN fromJIDResource VARCHAR(255) NULL;
ALTER TABLE ofMessageArchive ADD COLUMN toJIDResource VARCHAR(255) NULL;
-- Update database version
UPDATE jiveVersion SET version = 1 WHERE name = 'monitoring';
\ No newline at end of file
-- $Revision$
-- $Date$
ALTER TABLE ofMessageArchive ADD COLUMN fromJIDResource VARCHAR(255) NULL;
ALTER TABLE ofMessageArchive ADD COLUMN toJIDResource VARCHAR(255) NULL;
-- Update database version
UPDATE jiveVersion SET version = 1 WHERE name = 'monitoring';
\ No newline at end of file
-- $Revision$
-- $Date$
ALTER TABLE ofMessageArchive ADD COLUMN fromJIDResource VARCHAR(255) NULL;
ALTER TABLE ofMessageArchive ADD COLUMN toJIDResource VARCHAR(255) NULL;
-- Update database version
UPDATE jiveVersion SET version = 1 WHERE name = 'monitoring';
\ No newline at end of file
-- $Revision$
-- $Date$
ALTER TABLE ofMessageArchive ADD fromJIDResource VARCHAR(255) NULL;
ALTER TABLE ofMessageArchive ADD toJIDResource VARCHAR(255) NULL;
-- Update database version
UPDATE jiveVersion SET version = 1 WHERE name = 'monitoring';
commit;
\ No newline at end of file
-- $Revision$
-- $Date$
ALTER TABLE ofMessageArchive ADD COLUMN fromJIDResource VARCHAR(255) NULL;
ALTER TABLE ofMessageArchive ADD COLUMN toJIDResource VARCHAR(255) NULL;
-- Update database version
UPDATE jiveVersion SET version = 1 WHERE name = 'monitoring';
\ No newline at end of file
-- $Revision$
-- $Date$
ALTER TABLE ofMessageArchive ADD fromJIDResource NVARCHAR(255) NULL;
ALTER TABLE ofMessageArchive ADD toJIDResource NVARCHAR(255) NULL;
-- Update database version
UPDATE jiveVersion SET version = 1 WHERE name = 'monitoring';
\ No newline at end of file
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
{
// TODO: change the below to a separate property to allow archiving but disable/enable XEP-0136
String ENABLED = "conversation.metadataArchiving";
String INDEX_DIR = "archive.indexdir";
// Unnecessary since Open Archive Archive Manager no longer archives messages
String CONVERSATION_TIMEOUT = "conversation.idleTime";
}
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.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.JID;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;
/**
* Conversation events are only used when running in a cluster as a way to send to the senior cluster
* member information about a conversation that is taking place in this cluster node.
*
* @author Gaston Dombiak
*/
public class ConversationEvent implements Externalizable {
private Type type;
private Date date;
private String body;
private JID sender;
private JID receiver;
private JID roomJID;
private JID user;
private String nickname;
/**
* Do not use this constructor. It only exists for serialization purposes.
*/
public ConversationEvent() {
}
public void run(ConversationManager conversationManager) {
if (Type.chatMessageReceived == type) {
conversationManager.processMessage(sender, receiver, body, date);
}
else if (Type.roomDestroyed == type) {
conversationManager.roomConversationEnded(roomJID, date);
}
else if (Type.occupantJoined == type) {
conversationManager.joinedGroupConversation(roomJID, user, nickname, date);
}
else if (Type.occupantLeft == type) {
conversationManager.leftGroupConversation(roomJID, user, date);
// If there are no more occupants then consider the group conversarion over
MUCRoom mucRoom = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(roomJID).getChatRoom(roomJID.getNode());
if (mucRoom != null && mucRoom.getOccupantsCount() == 0) {
conversationManager.roomConversationEnded(roomJID, date);
}
}
else if (Type.nicknameChanged == type) {
conversationManager.leftGroupConversation(roomJID, user, date);
conversationManager.joinedGroupConversation(roomJID, user, nickname, new Date(date.getTime() + 1));
}
else if (Type.roomMessageReceived == type) {
conversationManager.processRoomMessage(roomJID, user, nickname, body, date);
}
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeInt(out, type.ordinal());
ExternalizableUtil.getInstance().writeLong(out, date.getTime());
ExternalizableUtil.getInstance().writeBoolean(out, sender != null);
if (sender != null) {
ExternalizableUtil.getInstance().writeSerializable(out, sender);
}
ExternalizableUtil.getInstance().writeBoolean(out, receiver != null);
if (receiver != null) {
ExternalizableUtil.getInstance().writeSerializable(out, receiver);
}
ExternalizableUtil.getInstance().writeBoolean(out, body != null);
if (body != null) {
ExternalizableUtil.getInstance().writeSafeUTF(out, body);
}
ExternalizableUtil.getInstance().writeBoolean(out, roomJID != null);
if (roomJID != null) {
ExternalizableUtil.getInstance().writeSerializable(out, roomJID);
}
ExternalizableUtil.getInstance().writeBoolean(out, user != null);
if (user != null) {
ExternalizableUtil.getInstance().writeSerializable(out, user);
}
ExternalizableUtil.getInstance().writeBoolean(out, nickname != null);
if (nickname != null) {
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
}
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
type = Type.values()[ExternalizableUtil.getInstance().readInt(in)];
date = new Date(ExternalizableUtil.getInstance().readLong(in));
if (ExternalizableUtil.getInstance().readBoolean(in)) {
sender = (JID) ExternalizableUtil.getInstance().readSerializable(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
receiver = (JID) ExternalizableUtil.getInstance().readSerializable(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
body = ExternalizableUtil.getInstance().readSafeUTF(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
roomJID = (JID) ExternalizableUtil.getInstance().readSerializable(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
user = (JID) ExternalizableUtil.getInstance().readSerializable(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
public static ConversationEvent chatMessageReceived(JID sender, JID receiver, String body, Date date) {
ConversationEvent event = new ConversationEvent();
event.type = Type.chatMessageReceived;
event.sender = sender;
event.receiver = receiver;
event.body = body;
event.date = date;
return event;
}
public static ConversationEvent roomDestroyed(JID roomJID, Date date) {
ConversationEvent event = new ConversationEvent();
event.type = Type.roomDestroyed;
event.roomJID = roomJID;
event.date = date;
return event;
}
public static ConversationEvent occupantJoined(JID roomJID, JID user, String nickname, Date date) {
ConversationEvent event = new ConversationEvent();
event.type = Type.occupantJoined;
event.roomJID = roomJID;
event.user = user;
event.nickname = nickname;
event.date = date;
return event;
}
public static ConversationEvent occupantLeft(JID roomJID, JID user, Date date) {
ConversationEvent event = new ConversationEvent();
event.type = Type.occupantLeft;
event.roomJID = roomJID;
event.user = user;
event.date = date;
return event;
}
public static ConversationEvent nicknameChanged(JID roomJID, JID user, String newNickname, Date date) {
ConversationEvent event = new ConversationEvent();
event.type = Type.nicknameChanged;
event.roomJID = roomJID;
event.user = user;
event.nickname = newNickname;
event.date = date;
return event;
}
public static ConversationEvent roomMessageReceived(JID roomJID, JID user, String nickname, String body,
Date date) {
ConversationEvent event = new ConversationEvent();
event.type = Type.roomMessageReceived;
event.roomJID = roomJID;
event.user = user;
event.nickname = nickname;
event.body = body;
event.date = date;
return event;
}
private static enum Type {
/**
* Event triggered when a room was destroyed.
*/
roomDestroyed,
/**
* Event triggered when a new occupant joins a room.
*/
occupantJoined,
/**
* Event triggered when an occupant left a room.
*/
occupantLeft,
/**
* Event triggered when an occupant changed his nickname in a room.
*/
nicknameChanged,
/**
* Event triggered when a room occupant sent a message to a room.
*/
roomMessageReceived,
/**
* Event triggered when a user sent a message to another user.
*/
chatMessageReceived
}
}
/**
* $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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import org.jivesoftware.openfire.archive.cluster.SendConversationEventsTask;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.reporting.util.TaskEngine;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.cache.CacheFactory;
/**
* Queue conversation events generated by this JVM and send them to the senior cluster
* member every 3 seconds. This is an optimization to reduce traffic between the cluster
* nodes specialy when under heavy conversations load.
*
* @author Gaston Dombiak
*/
public class ConversationEventsQueue {
private ConversationManager conversationManager;
/**
* Chat events that are pending to be sent to the senior cluster member.
* Key: Conversation Key; Value: List of conversation events.
*/
private final Map<String, List<ConversationEvent>> chatEvents = new HashMap<String, List<ConversationEvent>>();
/**
* Group chat events that are pending to be sent to the senior cluster member.
* Key: Conversation Key; Value: List of conversation events.
*/
private final Map<String, List<ConversationEvent>> roomEvents = new HashMap<String, List<ConversationEvent>>();
public ConversationEventsQueue(ConversationManager conversationManager, TaskEngine taskEngine) {
this.conversationManager = conversationManager;
// Schedule a task to do conversation archiving.
TimerTask sendTask = new TimerTask() {
@Override
public void run() {
// Move queued events to a temp place
List<ConversationEvent> eventsToSend = new ArrayList<ConversationEvent>();
synchronized (chatEvents) {
for (List<ConversationEvent> list : chatEvents.values()) {
// Just send the first and last event if we are not archiving messages
if (!ConversationEventsQueue.this.conversationManager.isMessageArchivingEnabled() &&
list.size() > 2) {
eventsToSend.add(list.get(0));
eventsToSend.add(list.get(list.size() - 1));
}
else {
// Send all events
eventsToSend.addAll(list);
}
}
// We can empty the queue now
chatEvents.clear();
}
synchronized (roomEvents) {
for (List<ConversationEvent> list : roomEvents.values()) {
eventsToSend.addAll(list);
}
// We can empty the queue now
roomEvents.clear();
}
// Send the queued events (from the temp place) to the senior cluster member
CacheFactory.doClusterTask(new SendConversationEventsTask(eventsToSend),
ClusterManager.getSeniorClusterMember().toByteArray());
}
};
taskEngine.scheduleAtFixedRate(sendTask, JiveConstants.SECOND * 3, JiveConstants.SECOND * 3);
}
/**
* Queues the one-to-one chat event to be later sent to the senior cluster member.
*
* @param conversationKey unique key that identifies the conversation.
* @param event conversation event.
*/
public void addChatEvent(String conversationKey, ConversationEvent event) {
synchronized (chatEvents) {
List<ConversationEvent> events = chatEvents.get(conversationKey);
if (events == null) {
events = new ArrayList<ConversationEvent>();
chatEvents.put(conversationKey, events);
}
events.add(event);
}
}
/**
* Queues the group chat event to be later sent to the senior cluster member.
*
* @param conversationKey unique key that identifies the conversation.
* @param event conversation event.
*/
public void addGroupChatEvent(String conversationKey, ConversationEvent event) {
synchronized (roomEvents) {
List<ConversationEvent> events = roomEvents.get(conversationKey);
if (events == null) {
events = new ArrayList<ConversationEvent>();
roomEvents.put(conversationKey, events);
}
events.add(event);
}
}
}
/**
* $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;
}
}
/**
* $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 java.util.Date;
/**
* Listens for conversations being created, finished, and updated. Note that listeners
* are notified using application threads so any long running processing tasks that result
* from notifications should be scheduled for separate threads.
*
* @see ConversationManager#addConversationListener(ConversationListener)
* @author Matt Tucker
*/
public interface ConversationListener {
/**
* A conversation was created.
*
* @param conversation the conversation.
*/
public void conversationCreated(Conversation conversation);
/**
* A conversation was updated, which means that a new message was sent between
* the participants.
*
* @param conversation the conversation.
* @param date the date the conversation was updated.
*/
public void conversationUpdated(Conversation conversation, Date date);
/**
* A conversation ended due to inactivity or because the maximum conversation time
* was hit.
*
* @param conversation the conversation.
*/
public void conversationEnded(Conversation conversation);
}
\ 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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.plugin.MonitoringPlugin;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.ParamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConversationPDFServlet extends HttpServlet {
private static final Logger Log = LoggerFactory.getLogger(ConversationPDFServlet.class);
@Override
public void init() throws ServletException {
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long conversationID = ParamUtils.getLongParameter(request, "conversationID", -1);
if (conversationID == -1) {
return;
}
MonitoringPlugin plugin = (MonitoringPlugin)XMPPServer.getInstance().getPluginManager().getPlugin(
MonitoringConstants.NAME);
ConversationManager conversationManager = (ConversationManager)plugin.getModule(ConversationManager.class);
Conversation conversation;
if (conversationID > -1) {
try {
conversation = new Conversation(conversationManager, conversationID);
ByteArrayOutputStream stream = new ConversationUtils().getConversationPDF(conversation);
// setting some response headers
response.setHeader("Expires", "0");
response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
// setting the content type
response.setContentType("application/pdf");
// the content length is needed for MSIE!!!
response.setContentLength(stream.size());
// write ByteArrayOutputStream to the ServletOutputStream
ServletOutputStream out = response.getOutputStream();
stream.writeTo(out);
out.flush();
}
catch (NotFoundException nfe) {
Log.error(nfe.getMessage(), nfe);
}
}
}
}
package org.jivesoftware.openfire.archive;
public class MonitoringConstants {
public static final String NAME = "monitoring";
}
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