/*
* 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.awt.Color;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.plugin.MonitoringPlugin;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.Image;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfWriter;
/**
* Utility class for asynchronous web calls for archiving tasks.
*
* @author Derek DeMoro
*/
public class ConversationUtils {
private static final Logger Log = LoggerFactory.getLogger(ConversationUtils.class);
/**
* Returns the status of the rebuilding of the messaging/metadata archives. This is done
* asynchronously.
*
* @return the status the rebuilding (0 - 100) where 100 is complete.
*/
public int getBuildProgress() {
// Get handle on the Monitoring plugin
MonitoringPlugin plugin =
(MonitoringPlugin)XMPPServer.getInstance().getPluginManager().getPlugin(
MonitoringConstants.NAME);
ArchiveIndexer archiveIndexer = (ArchiveIndexer)plugin.getModule(ArchiveIndexer.class);
Future<Integer> future = archiveIndexer.getIndexRebuildProgress();
if (future != null) {
try {
return future.get();
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
return -1;
}
public ConversationInfo getConversationInfo(long conversationID, boolean formatParticipants) {
// Create ConversationInfo bean
ConversationInfo info = new ConversationInfo();
// Get handle on the Monitoring plugin
MonitoringPlugin plugin =
(MonitoringPlugin)XMPPServer.getInstance().getPluginManager().getPlugin(
MonitoringConstants.NAME);
ConversationManager conversationmanager =
(ConversationManager)plugin.getModule(ConversationManager.class);
try {
Conversation conversation = conversationmanager.getConversation(conversationID);
info = toConversationInfo(conversation, formatParticipants);
}
catch (NotFoundException e) {
Log.error(e.getMessage(), e);
}
return info;
}
/**
* Retrieves all the existing conversations from the system.
*
* @return a Map of ConversationInfo objects.
*/
public Map<String, ConversationInfo> getConversations(boolean formatParticipants) {
Map<String, ConversationInfo> cons = new HashMap<String, ConversationInfo>();
MonitoringPlugin plugin = (MonitoringPlugin)XMPPServer.getInstance().getPluginManager()
.getPlugin(MonitoringConstants.NAME);
ConversationManager conversationManager =
(ConversationManager)plugin.getModule(ConversationManager.class);
Collection<Conversation> conversations = conversationManager.getConversations();
List<Conversation> lConversations =
Arrays.asList(conversations.toArray(new Conversation[conversations.size()]));
for (Iterator<Conversation> i = lConversations.iterator(); i.hasNext();) {
Conversation con = i.next();
ConversationInfo info = toConversationInfo(con, formatParticipants);
cons.put(Long.toString(con.getConversationID()), info);
}
return cons;
}
public ByteArrayOutputStream getConversationPDF(Conversation conversation) {
Font red = FontFactory
.getFont(FontFactory.HELVETICA, 12f, Font.BOLD, new Color(0xFF, 0x00, 0x00));
Font blue = FontFactory
.getFont(FontFactory.HELVETICA, 12f, Font.ITALIC, new Color(0x00, 0x00, 0xFF));
Font black = FontFactory.getFont(FontFactory.HELVETICA, 12f, Font.BOLD, Color.BLACK);
Map<String, Font> colorMap = new HashMap<String, Font>();
if (conversation != null) {
Collection<JID> set = conversation.getParticipants();
int count = 0;
for (JID jid : set) {
if (conversation.getRoom() == null) {
if (count == 0) {
colorMap.put(jid.toString(), blue);
}
else {
colorMap.put(jid.toString(), red);
}
count++;
}
else {
colorMap.put(jid.toString(), black);
}
}
}
return buildPDFContent(conversation, colorMap);
}
private ByteArrayOutputStream buildPDFContent(Conversation conversation,
Map<String, Font> colorMap) {
Font roomEvent = FontFactory
.getFont(FontFactory.HELVETICA, 12f, Font.ITALIC, new Color(0xFF, 0x00, 0xFF));
try {
Document document = new Document(PageSize.A4, 50, 50, 50, 50);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter writer = PdfWriter.getInstance(document, baos);
writer.setPageEvent(new PDFEventListener());
document.open();
Paragraph p = new Paragraph(
LocaleUtils.getLocalizedString("archive.search.pdf.title", MonitoringConstants.NAME),
FontFactory.getFont(FontFactory.HELVETICA,
18, Font.BOLD));
document.add(p);
document.add(Chunk.NEWLINE);
ConversationInfo coninfo = new ConversationUtils()
.getConversationInfo(conversation.getConversationID(), false);
String participantsDetail;
if (coninfo.getAllParticipants() == null) {
participantsDetail = coninfo.getParticipant1() + ", " + coninfo.getParticipant2();
}
else {
participantsDetail = String.valueOf(coninfo.getAllParticipants().length);
}
Paragraph chapterTitle = new Paragraph(
LocaleUtils
.getLocalizedString("archive.search.pdf.participants", MonitoringConstants.NAME) +
" " + participantsDetail,
FontFactory.getFont(FontFactory.HELVETICA, 12,
Font.BOLD));
document.add(chapterTitle);
Paragraph startDate = new Paragraph(
LocaleUtils.getLocalizedString("archive.search.pdf.startdate", MonitoringConstants.NAME) +
" " +
coninfo.getDate(),
FontFactory.getFont(FontFactory.HELVETICA, 12,
Font.BOLD));
document.add(startDate);
Paragraph duration = new Paragraph(
LocaleUtils.getLocalizedString("archive.search.pdf.duration", MonitoringConstants.NAME) +
" " +
coninfo.getDuration(),
FontFactory.getFont(FontFactory.HELVETICA, 12,
Font.BOLD));
document.add(duration);
Paragraph messageCount = new Paragraph(
LocaleUtils
.getLocalizedString("archive.search.pdf.messagecount", MonitoringConstants.NAME) +
" " +
conversation.getMessageCount(),
FontFactory.getFont(FontFactory.HELVETICA, 12,
Font.BOLD));
document.add(messageCount);
document.add(Chunk.NEWLINE);
Paragraph messageParagraph;
for (ArchivedMessage message : conversation.getMessages()) {
String time = JiveGlobals.formatTime(message.getSentDate());
String from = message.getFromJID().getNode();
if (conversation.getRoom() != null) {
from = message.getToJID().getResource();
}
String body = message.getBody();
String prefix;
if (!message.isRoomEvent()) {
prefix = "[" + time + "] " + from + ": ";
Font font = colorMap.get(message.getFromJID().toString());
if (font == null) {
font = colorMap.get(message.getFromJID().toBareJID());
}
if (font == null) {
font = FontFactory.getFont(FontFactory.HELVETICA, 12f, Font.BOLD, Color.BLACK);
}
messageParagraph = new Paragraph(new Chunk(prefix, font));
}
else {
prefix = "[" + time + "] ";
messageParagraph = new Paragraph(new Chunk(prefix, roomEvent));
}
messageParagraph.add(body);
messageParagraph.add(" ");
document.add(messageParagraph);
}
document.close();
return baos;
}
catch (DocumentException e) {
Log.error("error creating PDF document: " + e.getMessage(), e);
return null;
}
}
private ConversationInfo toConversationInfo(Conversation conversation,
boolean formatParticipants) {
final ConversationInfo info = new ConversationInfo();
// Set participants
Collection<JID> col = conversation.getParticipants();
if (conversation.getRoom() == null) {
JID user1 = (JID)col.toArray()[0];
info.setParticipant1(formatJID(formatParticipants, user1));
JID user2 = (JID)col.toArray()[1];
info.setParticipant2(formatJID(formatParticipants, user2));
}
else {
info.setConversationID(conversation.getConversationID());
JID[] occupants = col.toArray(new JID[col.size()]);
String[] jids = new String[col.size()];
for (int i = 0; i < occupants.length; i++) {
jids[i] = formatJID(formatParticipants, occupants[i]);
}
info.setAllParticipants(jids);
}
Map<String, String> cssLabels = new HashMap<String, String>();
int count = 0;
for (JID jid : col) {
if (!cssLabels.containsKey(jid.toString())) {
if (conversation.getRoom() == null) {
if (count % 2 == 0) {
cssLabels.put(jid.toBareJID(), "conversation-label2");
}
else {
cssLabels.put(jid.toBareJID(), "conversation-label1");
}
count++;
}
else {
cssLabels.put(jid.toString(), "conversation-label4");
}
}
}
// Set date
info.setDate(JiveGlobals.formatDateTime(conversation.getStartDate()));
info.setLastActivity(JiveGlobals.formatTime(conversation.getLastActivity()));
// Create body.
final StringBuilder builder = new StringBuilder();
builder.append("<table width=100%>");
for (ArchivedMessage message : conversation.getMessages()) {
String time = JiveGlobals.formatTime(message.getSentDate());
String from = message.getFromJID().getNode();
if (conversation.getRoom() != null) {
from = message.getToJID().getResource();
}
from = StringUtils.escapeHTMLTags(from);
String cssLabel = cssLabels.get(message.getFromJID().toBareJID());
String body = StringUtils.escapeHTMLTags(message.getBody());
builder.append("<tr valign=top>");
if (!message.isRoomEvent()) {
builder.append("<td width=1% nowrap class=" + cssLabel + ">").append("[")
.append(time).append("]").append("</td>");
builder.append("<td width=1% class=" + cssLabel + ">").append(from).append(": ")
.append("</td>");
builder.append("<td class=conversation-body>").append(body).append("</td");
}
else {
builder.append("<td width=1% nowrap class=conversation-label3>").append("[")
.append(time).append("]").append("</td>");
builder.append("<td colspan=2 class=conversation-label3><i>").append(body)
.append("</i></td");
}
builder.append("</tr>");
}
if (conversation.getMessages().size() == 0) {
builder.append("<span class=small-description>" +
LocaleUtils.getLocalizedString("archive.search.results.archive_disabled",
MonitoringConstants.NAME) +
"</a>");
}
info.setBody(builder.toString());
// Set message count
info.setMessageCount(conversation.getMessageCount());
long duration =
(conversation.getLastActivity().getTime() - conversation.getStartDate().getTime());
info.setDuration(duration);
return info;
}
private String formatJID(boolean html, JID jid) {
String formattedJID;
if (html) {
UserManager userManager = UserManager.getInstance();
if (XMPPServer.getInstance().isLocal(jid) &&
userManager.isRegisteredUser(jid.getNode())) {
formattedJID = "<a href='/user-properties.jsp?username=" +
jid.getNode() + "'>" + jid.toBareJID() + "</a>";
}
else {
formattedJID = jid.toBareJID();
}
}
else {
formattedJID = jid.toBareJID();
}
return formattedJID;
}
class PDFEventListener extends PdfPageEventHelper {
@Override
public void onEndPage(PdfWriter writer, Document document) {
PdfContentByte cb = writer.getDirectContent();
try {
cb.setColorStroke(new Color(156, 156, 156));
cb.setLineWidth(2);
cb.moveTo(document.leftMargin(), document.bottomMargin() - 5);
cb.lineTo(document.getPageSize().width() - document.rightMargin(),
document.bottomMargin() - 5);
cb.stroke();
ClassLoader classLoader = ConversationUtils.class.getClassLoader();
Enumeration<URL> providerEnum = classLoader.getResources("images/pdf_generatedbyof.gif");
while (providerEnum.hasMoreElements()) {
Image gif = Image.getInstance(providerEnum.nextElement());
cb.addImage(gif, 221, 0, 0, 28, (int)document.leftMargin(),
(int)document.bottomMargin() - 35);
}
}
catch (Exception e) {
Log.error("error drawing PDF footer: " + e.getMessage());
}
cb.saveState();
}
}
}