/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2005-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.plugin.ofmeet;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.util.BASE64DecoderStream;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.muc.spi.*;
import org.jivesoftware.openfire.forms.spi.*;
import org.jivesoftware.openfire.forms.*;
import org.jivesoftware.openfire.plugin.spark.*;
import org.jivesoftware.database.DbConnectionManager;
import org.xmpp.packet.JID;
import org.xmpp.packet.IQ;
import org.dom4j.*;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.*;
import javax.mail.event.MessageCountAdapter;
import javax.mail.event.MessageCountEvent;
import java.security.Security;
import java.util.*;
import java.net.*;
import java.io.*;
import java.sql.Connection;
import java.sql.*;
import java.text.SimpleDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.json.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.workgroup.*;
import org.jivesoftware.smackx.workgroup.user.Workgroup;
/**
* The meetings email listener service connects to an email server using IMAP and listens for new emails.
* Bookmarks are created for all copied users, PDF attachments and planned meeting dates when new email messages are found.
*
* @author Gaston Dombiak
*/
public class EmailListener {
private static final EmailListener instance = new EmailListener();
private static final Logger Log = LoggerFactory.getLogger(EmailListener.class);
/**
* Message listener that will process new emails found in the IMAP server.
*/
private MessageCountAdapter messageListener;
private Folder folder;
private boolean started = false;
private String downloadHome = JiveGlobals.getHomeDirectory() + File.separator + "resources" + File.separator + "spank" + File.separator + "ofmeet-cdn" + File.separator + "download";
private UserManager userManager = XMPPServer.getInstance().getUserManager();
private static Map<String, Presence> workgroupPresence = new HashMap<String, Presence>();
private static Map workgroups = new HashMap();
private static Map<String, XMPPConnection> globalConnections = new HashMap<String, XMPPConnection>();
public static EmailListener getInstance() {
return instance;
}
private EmailListener() {
}
/**
* Returns true if a connection to the IMAP server was successful.
*
* @param host Host to connect to.
* @param port Port to connect over.
* @param isSSLEnabled True if an SSL connection will be attempted.
* @param user Username to use for authentication.
* @param password Password to use for authentication.
* @param folderName Folder to check.
* @return true if a connection to the IMAP server was successful.
*/
public static boolean testConnection(String host, int port, boolean isSSLEnabled, String user, String password,
String folderName) {
Folder folder = openFolder(host, port, isSSLEnabled, user, password, folderName);
boolean success = folder != null && folder.isOpen();
closeFolder(folder, null);
return success;
}
/**
* Opens a connection to the IMAP server and listen for new messages.
*/
public void start() {
// Check that the listner service is not running
if (started) {
return;
}
Thread thread = new Thread("Email Listener Thread") {
@Override
public void run() {
// Open the email folder and keep it
folder = openFolder(getHost(), getPort(), isSSLEnabled(), getUser(), getPassword(), getFolder());
if (folder != null) {
// Listen for new email messages until #stop is requested
listenMessages();
}
}
};
thread.setDaemon(true);
thread.start();
started = true;
}
/**
* Closes the active connection to the IMAP server.
*/
public void stop() {
closeFolder(folder, messageListener);
started = false;
folder = null;
messageListener = null;
}
private void listenMessages() {
try {
// Add messageCountListener to listen for new messages
messageListener = new MessageCountAdapter() {
@Override
public void messagesAdded(MessageCountEvent ev) {
Message[] msgs = ev.getMessages();
// Send new messages to specified users
for (Message msg : msgs) {
try {
listenMessage(msg);
}
catch (Exception e) {
Log.error("Error while sending new email message", e);
}
}
}
};
folder.addMessageCountListener(messageListener);
// Check mail once in "freq" MILLIseconds
int freq = getFrequency();
boolean supportsIdle = false;
try {
if (folder instanceof IMAPFolder) {
IMAPFolder f = (IMAPFolder) folder;
f.idle();
supportsIdle = true;
}
}
catch (FolderClosedException fex) {
throw fex;
}
catch (MessagingException mex) {
supportsIdle = false;
}
while (messageListener != null) {
if (supportsIdle && folder instanceof IMAPFolder) {
IMAPFolder f = (IMAPFolder) folder;
try {
f.idle();
} catch (Exception e) {}
}
else {
Thread.sleep(freq); // sleep for freq milliseconds
// This is to force the IMAP server to send us
// EXISTS notifications.
if (folder != null && folder.isOpen()) {
folder.getMessageCount();
}
}
}
}
catch (Exception ex) {
Log.error("Error listening new email messages", ex);
}
}
private void listenMessage(Message message) throws Exception {
String subject = message.getSubject();
List<String> userCollection = new ArrayList<String>();
Log.info("New email has been received " + subject);
if (subject.startsWith("Openfire Meetings: ")) return; // email listener is a participant, ignore email
User fromUser = null;
for (Address address: message.getFrom())
{
User user = getUserFromEmailAddress(address);
if (user != null)
{
userCollection.add(user.getUsername());
if (fromUser == null) fromUser = user;
}
}
String fastpathPrefix = getFastpathPrefix();
if (fastpathPrefix != null)
{
if (subject.startsWith(fastpathPrefix))
{
if ((isFastpathAuthEnabled() && fromUser != null) || isFastpathAuthEnabled() == false)
{
String workgroup = subject.substring(fastpathPrefix.length()).trim();
processWorkgroupRequest(workgroup, message, fromUser);
}
return;
}
}
Meeting meeting = new Meeting();
if (message.isMimeType("multipart/*"))
{
Multipart mp = (Multipart) message.getContent();
int count = mp.getCount();
for (int i = 0; i < count; i++)
{
createBookmarksForPDFs(mp.getBodyPart(i), subject, userCollection, meeting);
}
}
if (fromUser != null && (subject.startsWith("Re: Openfire Meetings: ") || subject.startsWith("RE: Openfire Meetings:")))
{
Bookmark bookmark = GetBookmarkByName(subject.substring(23));
if (bookmark != null) // user replies email listener, if user is online, send URL direct to ofmeet web client
{
Log.info("Found existing bookmark for " + bookmark.getProperty("url"));
Collection<ClientSession> sessions = SessionManager.getInstance().getSessions(fromUser.getUsername());
for (ClientSession session : sessions)
{
Log.info("Found existing session, redirecting to " + bookmark.getProperty("url"));
sendMessage(session.getAddress(), session.getAddress(), bookmark.getProperty("url"));
}
return;
}
}
if (fromUser != null && subject.startsWith("Canceled: "))
{
Bookmark bookmark = GetBookmarkByName(subject.substring(10));
if (bookmark != null && meeting.cancel)
{
bookmark.setProperty("calendar", "[]");
Log.info("Removing event for meeting planner \n" + meeting.body);
}
return;
}
for (Address address: message.getAllRecipients())
{
User user = getUserFromEmailAddress(address);
if (user != null) userCollection.add(user.getUsername());
}
if (fromUser != null && userCollection.size() > 0 )
{
String meetingTitle = "Openfire Meetings: " + subject;
Bookmark bookmark = GetBookmarkByName(subject);
JID fromJid = XMPPServer.getInstance().createJID(fromUser.getUsername(), null);
if (bookmark == null)
{
String roomName = "ofmeet-" + System.currentTimeMillis();
String roomJid = roomName + "@conference." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
bookmark = new Bookmark(Bookmark.Type.group_chat, subject, roomJid);
String id = "" + bookmark.getBookmarkID() + System.currentTimeMillis();
String rootUrlSecure = JiveGlobals.getProperty("ofmeet.root.url.secure", "https://" + XMPPServer.getInstance().getServerInfo().getHostname() + ":" + JiveGlobals.getProperty("httpbind.port.secure", "7443"));
String url = rootUrlSecure + "/ofmeet/?b=" + id;
bookmark.setProperty("url", url);
bookmark.setProperty("autojoin", "true");
bookmark.setGlobalBookmark(false);
createRoom(roomName, subject, fromUser.getUsername(), userCollection);
}
if (meeting.startDate != null && meeting.endDate != null)
{
try {
String roomId = (new JID(bookmark.getValue())).getNode();
if (meeting.body == null) meeting.body = subject;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'Z'");
String startDate = dateFormat.format(meeting.startDate);
String endDate = dateFormat.format(meeting.endDate);
if (meeting.request)
{
String json = "[{\"processed\": false, \"id\": \"" + System.currentTimeMillis() + "\", \"start\": " + meeting.startDate.getTime() + ", \"end\": " + meeting.endDate.getTime() + ", \"startTime\": \"" + endDate + "\", \"endTime\": \"" + endDate + "\", \"title\": \"" + subject + "\", \"description\": \"" + meeting.body + "\", \"room\": \"" + roomId + "\"}]";
bookmark.setProperty("calendar", json);
Log.info("Adding event for meeting planner \n" + meeting.body);
}
} catch (Exception e) {
Log.error("Error create/cancel meeting planner event", e);
}
} else {
meeting.startDate = new java.util.Date(System.currentTimeMillis());
meeting.endDate = new java.util.Date(System.currentTimeMillis() + 3600000);
Log.info("Immediate meeting, no event meeting planner\n" + meeting.body);
}
bookmark.setUsers(userCollection);
for (String username: userCollection)
{
JSONObject meetingJSON = new JSONObject();
meetingJSON.put("startTime", meeting.startDate.getTime());
meetingJSON.put("endTime", meeting.endDate.getTime());
meetingJSON.put("description", meeting.body == null ? bookmark.getName() : meeting.body);
meetingJSON.put("title", meetingTitle);
meetingJSON.put("room", bookmark.getValue());
OfMeetPlugin.self.processMeeting(meetingJSON, username, bookmark.getProperty("url"));
Collection<ClientSession> sessions = SessionManager.getInstance().getSessions(username);
for (ClientSession session : sessions)
{
sendMessage(fromJid, session.getAddress(), bookmark.getProperty("url"));
}
}
}
}
private void sendMessage(JID from, JID to, String body)
{
Log.info("sendMessage: " + from + " " + to + " " + body);
org.xmpp.packet.Message message = new org.xmpp.packet.Message();
message.setTo(to);
message.setFrom(from);
message.setType(org.xmpp.packet.Message.Type.chat);
message.setBody(body);
XMPPServer.getInstance().getMessageRouter().route(message);
}
private User getUserFromEmailAddress(Address address)
{
String from = address.toString();
User theUser = null;
int ltIndex = from.indexOf('<');
int atIndex = from.indexOf('@');
int gtIndex = from.indexOf('>');
if ((ltIndex!=-1) && (atIndex!=-1) && (gtIndex!=-1) && (ltIndex<atIndex) && (atIndex<gtIndex) )
{
from = from.substring(ltIndex+1, gtIndex);
Log.info("getUserFromEmailAddress: found email address " + from);
Collection<User> users = userManager.findUsers(new HashSet<String>(Arrays.asList("Email")), from);
for (User user : users)
{
Log.info("getUserFromEmailAddress: matched email address " + from + " " + user.getUsername());
theUser = user;
}
}
return theUser;
}
private Bookmark GetBookmarkByName(String name)
{
Bookmark bookmark = null;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement("SELECT bookmarkID from ofBookmark where bookmarkName=?");
pstmt.setString(1, name);
rs = pstmt.executeQuery();
if (rs.next()) {
long bookmarkID = rs.getLong(1);
try {
bookmark = new Bookmark(bookmarkID);
}
catch (Exception e) {
}
}
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return bookmark;
}
private void createBookmarksForPDFs(Part part, String subject, List<String> userCollection, Meeting meeting) throws Exception {
/*
* Using isMimeType to determine the content type avoids
* fetching the actual content data until we need it.
*/
if (part.isMimeType("text/plain"))
{
meeting.body = (String) part.getContent();
meeting.body = meeting.body.replace("\n", " ").replace("\r", "").replace("\t", "").replace("\"", "'");
Log.info("Found body \n" + meeting.body);
}
else if (part.isMimeType("multipart/*")) {
Log.info("Found embedded multipart");
}
else if (part.isMimeType("message/rfc822")) {
Log.info("Found nested email");
}
else {
String fileName = part.getFileName();
Object o = part.getContent();
if (fileName != null && fileName.endsWith(".pdf") && o instanceof InputStream)
{
Log.info("Found PDF " + fileName);
OutputStream output = new FileOutputStream(downloadHome + File.separator + fileName);
try {
InputStream is = (InputStream) o;
byte[] buffer = new byte[8 * 1024];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1)
{
output.write(buffer, 0, bytesRead);
}
} finally {
output.close();
}
String pdfTitle = subject + " - " + fileName;
String rootUrlPlain = JiveGlobals.getProperty("ofmeet.root.url.plain", "http://" + XMPPServer.getInstance().getServerInfo().getHostname() + ":" + JiveGlobals.getProperty("httpbind.port.plain", "7070"));
String pdfUrl = rootUrlPlain + "/ofmeet-cdn/download/" + fileName;
Bookmark bookmark = GetBookmarkByName(pdfTitle);
if (bookmark == null)
{
String roomJid = "ofmeet-" + System.currentTimeMillis() + "@conference." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
bookmark = new Bookmark(Bookmark.Type.url, pdfTitle, pdfUrl);
}
String id = "" + bookmark.getBookmarkID() + System.currentTimeMillis();
String rootUrlSecure = JiveGlobals.getProperty("ofmeet.root.url.secure", "https://" + XMPPServer.getInstance().getServerInfo().getHostname() + ":" + JiveGlobals.getProperty("httpbind.port.secure", "7443"));
String url = rootUrlSecure + "/ofmeet/?b=" + id;
bookmark.setProperty("url", pdfUrl);
bookmark.setGlobalBookmark(false);
bookmark.setUsers(userCollection);
}
else {
if (o instanceof BASE64DecoderStream)
{
String contentType = part.getContentType();
if (contentType.indexOf("TEXT/CALENDAR") > -1)
{
meeting.request = contentType.indexOf("method=REQUEST") > -1;
meeting.cancel = contentType.indexOf("method=CANCEL") > -1;
Log.info("Found Calendar data request=" + meeting.request + ", cancel=" + meeting.cancel);
try {
BASE64DecoderStream is = (BASE64DecoderStream) o;
byte[] buffer = new byte[8 * 1024];
int bytesRead;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'hhmmss");
while ((bytesRead = is.read(buffer)) != -1)
{
String iCalLine = new String(buffer, 0, bytesRead);
String lines[] = iCalLine.split("\\r?\\n");
meeting.startDate = getDate(dateFormat, lines, "DTSTART;TZID=");
meeting.endDate = getDate(dateFormat, lines, "DTEND;TZID=");
}
Log.info("Found Calendar start=" + meeting.startDate.getTime() + ", end=" + meeting.endDate.getTime());
} catch (Exception e) {
Log.error("Error decoding start or end date", e);
}
}
} else {
Log.info("Found PDF " + fileName);
Log.info("Found unknown attachment " + part.getFileName() + " " + part.getContentType());
Log.info(part.getContent().toString());
}
}
}
}
private java.util.Date getDate(SimpleDateFormat dateFormat, String lines[], String prefix)
{
java.util.Date date = null;
for (int i=0; i<lines.length; i++)
{
if (lines[i].indexOf(prefix) > -1)
{
try {
date = dateFormat.parse(lines[i].substring(lines[i].indexOf(":") + 1));
} catch (Exception e) {
Log.error("Error parsing date in line " + lines[i]);
}
}
}
return date;
}
private void createRoom(String roomName, String title, String owner, List<String> userCollection) throws NotAllowedException
{
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService("conference").getChatRoom(roomName, XMPPServer.getInstance().createJID(owner, null));
configureRoom(room, title, owner);
for (String username: userCollection)
{
try {
room.addAdmin(XMPPServer.getInstance().createJID(username, null), room.getRole());
} catch (Exception e) {
Log.error("createRoom set admin role failed for " + username);
}
}
}
private static Folder openFolder(String host, Integer port, Boolean isSSLEnabled, String user, String password,
String folder) {
if (host == null || port == null || isSSLEnabled == null || user == null || password == null || folder == null) {
return null;
}
try {
Properties props = System.getProperties();
props.setProperty("mail.imap.host", host);
props.setProperty("mail.imap.port", String.valueOf(port));
props.setProperty("mail.imap.connectiontimeout", String.valueOf(10 * 1000));
// Allow messages with a mix of valid and invalid recipients to still be sent.
props.setProperty("mail.debug", JiveGlobals.getProperty("ofmeet.email.listener.debug", "true"));
// Get a Session object
Session session = Session.getInstance(props, null);
// Get a Store object
Store store = session.getStore(isSSLEnabled ? "imaps" : "imap");
// Connect
store.connect(host, user, password);
// Open a Folder
Folder newFolder = store.getFolder(folder);
if (newFolder == null || !newFolder.exists()) {
Log.error("Invalid email folder: " + folder);
return null;
}
newFolder.open(Folder.READ_WRITE);
return newFolder;
}
catch (Exception e) {
Log.error("Error while initializing email listener", e);
}
return null;
}
private static void closeFolder(Folder folder, MessageCountAdapter messageListener) {
if (folder != null) {
if (messageListener != null) {
folder.removeMessageCountListener(messageListener);
}
try {
folder.close(false);
}
catch (MessagingException e) {
Log.error("Error closing folder", e);
}
}
}
/**
* Returns the host where the IMAP server is running or <tt>null</tt> if none was defined.
*
* @return the host where the IMAP server is running or null if none was defined.
*/
public String getHost() {
return JiveGlobals.getProperty("ofmeet.email.listener.host");
}
/**
* Sets the host where the IMAP server is running or <tt>null</tt> if none was defined.
*
* @param host the host where the IMAP server is running or null if none was defined.
*/
public void setHost(String host) {
JiveGlobals.setProperty("ofmeet.email.listener.host", host);
}
/**
* Returns the port where the IMAP server is listening. By default unsecured connections
* use port 143 and secured ones use 993.
*
* @return port where the IMAP server is listening.
*/
public int getPort() {
return JiveGlobals.getIntProperty("ofmeet.email.listener.port", isSSLEnabled() ? 993 : 143);
}
/**
* Sets the port where the IMAP server is listening. By default unsecured connections
* use port 143 and secured ones use 993.
*
* @param port port where the IMAP server is listening.
*/
public void setPort(int port) {
JiveGlobals.setProperty("ofmeet.email.listener.port", Integer.toString(port));
}
/**
* Returns the user to use to connect to the IMAP server. A null value means that
* this property needs to be configured to be used.
*
* @return the user to use to connect to the IMAP server.
*/
public String getUser() {
return JiveGlobals.getProperty("ofmeet.email.listener.user");
}
/**
* Sets the user to use to connect to the IMAP server. A null value means that
* this property needs to be configured to be used.
*
* @param user the user to use to connect to the IMAP server.
*/
public void setUser(String user) {
JiveGlobals.setProperty("ofmeet.email.listener.user", user);
}
/**
* Returns the password to use to connect to the IMAP server. A null value means that
* this property needs to be configured to be used.
*
* @return the password to use to connect to the IMAP server.
*/
public String getPassword() {
return JiveGlobals.getProperty("ofmeet.email.listener.password");
}
/**
* Sets the password to use to connect to the IMAP server. A null value means that
* this property needs to be configured to be used.
*
* @param password the password to use to connect to the IMAP server.
*/
public void setPassword(String password) {
JiveGlobals.setProperty("ofmeet.email.listener.password", password);
}
/**
* Returns the name of the fastpath prefix.
*
* @return the name of the fastpath prefix.
*/
public String getFastpathPrefix() {
return JiveGlobals.getProperty("ofmeet.email.listener.fastpath.prefix");
}
/**
* Sets the name of the fastpath prefix.
*
* @param folder the name of the fastpath prefix.
*/
public void setFastpathPrefix(String prefix) {
JiveGlobals.setProperty("ofmeet.email.listener.fastpath.prefix", prefix);
}
/**
* Returns the name of the folder. In some Stores, name can be an absolute path if
* it starts with the hierarchy delimiter. Else it is interpreted relative to the
* 'root' of this namespace.
*
* @return the name of the folder.
*/
public String getFolder() {
return JiveGlobals.getProperty("ofmeet.email.listener.folder");
}
/**
* Sets the name of the folder. In some Stores, name can be an absolute path if
* it starts with the hierarchy delimiter. Else it is interpreted relative to the
* 'root' of this namespace.
*
* @param folder the name of the folder.
*/
public void setFolder(String folder) {
JiveGlobals.setProperty("ofmeet.email.listener.folder", folder);
}
/**
* Returns the milliseconds to wait to check for new emails. This frequency
* is used if the IMAP server does not support idle.
*
* @return the milliseconds to wait to check for new emails.
*/
public int getFrequency() {
return JiveGlobals.getIntProperty("ofmeet.email.listener.frequency", 5 * 60 * 1000);
}
/**
* Sets the milliseconds to wait to check for new emails. This frequency
* is used if the IMAP server does not support idle.
*
* @param frequency the milliseconds to wait to check for new emails.
*/
public void setFrequency(int frequency) {
JiveGlobals.setProperty("ofmeet.email.listener.frequency", Integer.toString(frequency));
}
/**
* Returns true if fastpath authentication is required
*
* @return true if fastpath authentication is required
*/
public boolean isFastpathAuthEnabled() {
return JiveGlobals.getBooleanProperty("ofmeet.email.listener.fastpath.auth", false);
}
/**
* Sets if fastpath authentication is required.
*
* @param enabled true if fastpath authentication is required
*/
public void setFastpathAuthEnabled(boolean enabled) {
JiveGlobals.setProperty("ofmeet.email.listener.fastpath.auth", Boolean.toString(enabled));
}
/**
* Returns true if SSL is enabled to connect to the server.
*
* @return true if SSL is enabled to connect to the server.
*/
public boolean isSSLEnabled() {
return JiveGlobals.getBooleanProperty("ofmeet.email.listener.ssl", false);
}
/**
* Sets if SSL is enabled to connect to the server.
*
* @param enabled true if SSL is enabled to connect to the server.
*/
public void setSSLEnabled(boolean enabled) {
JiveGlobals.setProperty("ofmeet.email.listener.ssl", Boolean.toString(enabled));
}
public Collection<String> getUsers() {
String users = JiveGlobals.getProperty("ofmeet.email.listener.users");
if (users == null || users.trim().length() == 0) {
Collection<String> admins = new ArrayList<String>();
for (JID jid : XMPPServer.getInstance().getAdmins()) {
admins.add(jid.toString());
}
return admins;
}
return StringUtils.stringToCollection(users);
}
public void setUsers(Collection<String> users) {
JiveGlobals.setProperty("ofmeet.email.listener.users", StringUtils.collectionToString(users));
}
private void configureRoom(MUCRoom room, String title, String owner)
{
Log.info( "configureRoom " + room.getID());
FormField field;
XDataFormImpl dataForm = new XDataFormImpl(DataForm.TYPE_SUBMIT);
field = new XFormFieldImpl("muc#roomconfig_roomdesc");
field.setType(FormField.TYPE_TEXT_SINGLE);
field.addValue(title);
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_roomname");
field.setType(FormField.TYPE_TEXT_SINGLE);
field.addValue(room.getName());
dataForm.addField(field);
field = new XFormFieldImpl("FORM_TYPE");
field.setType(FormField.TYPE_HIDDEN);
field.addValue("http://jabber.org/protocol/muc#roomconfig");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_changesubject");
field.addValue("1");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_maxusers");
field.addValue("30");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_presencebroadcast");
field.addValue("moderator");
field.addValue("participant");
field.addValue("visitor");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_publicroom");
field.addValue("1");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_persistentroom");
field.addValue("1");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_moderatedroom");
field.addValue("0");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_membersonly");
field.addValue("0");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_allowinvites");
field.addValue("1");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_passwordprotectedroom");
field.addValue("0");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_whois");
field.addValue("moderator");
dataForm.addField(field);
field = new XFormFieldImpl("muc#roomconfig_enablelogging");
field.addValue("1");
dataForm.addField(field);
field = new XFormFieldImpl("x-muc#roomconfig_canchangenick");
field.addValue("1");
dataForm.addField(field);
field = new XFormFieldImpl("x-muc#roomconfig_registration");
field.addValue("1");
dataForm.addField(field);
// Keep the existing list of admins
field = new XFormFieldImpl("muc#roomconfig_roomadmins");
for (JID jid : room.getAdmins()) {
field.addValue(jid.toString());
}
dataForm.addField(field);
String domainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
field = new XFormFieldImpl("muc#roomconfig_roomowners");
field.addValue(owner + "@" + domainName);
dataForm.addField(field);
// Create an IQ packet and set the dataform as the main fragment
IQ iq = new IQ(IQ.Type.set);
Element element = iq.setChildElement("query", "http://jabber.org/protocol/muc#owner");
element.add(dataForm.asXMLElement());
try
{
room.getIQOwnerHandler().handleIQ(iq, room.getRole());
} catch (Exception e) {
Log.error("configureRoom exception " + e);
}
}
private class Meeting
{
public java.util.Date startDate = null;
public java.util.Date endDate= null;
public String body = null;
public boolean request = false;
public boolean cancel = false;
}
private void sendWorkgroupEmail(String email, String workgroupNodeName, String template, String room)
{
Log.info("sendWorkgroupEmail " + workgroupNodeName + " " + email + " " + room + "\n" + template);
String videourl = "https://" + XMPPServer.getInstance().getServerInfo().getHostname() + ":" + JiveGlobals.getProperty("httpbind.port.secure", "7443") + "/ofmeet/?r=" + room;
String audiourl = videourl + "&novideo=true";
String title = "RE:" + getFastpathPrefix() + workgroupNodeName;
HashMap variables = new HashMap<String, String>();
variables.put("domain", XMPPServer.getInstance().getServerInfo().getXMPPDomain());
variables.put("videourl", videourl);
variables.put("audiourl", audiourl);
variables.put("workgroup", workgroupNodeName);
OfMeetPlugin.self.sendEmail(email, email, title, OfMeetPlugin.self.replaceTokens(template, variables), null);
}
private void processWorkgroupRequest(String workgroupNodeName, Message message, User fromUser)
{
Log.info("processWorkgroupRequest " + workgroupNodeName);
String template = JiveGlobals.getProperty("ofmeet.email.workgroup.unavailable.template", "Hello,\n\nWorkgroup [workgroup] is not taking requests at this moment.\nPlease try later\n\nAdministrator - [domain]");
try
{
String userid = null;
String username = null;
for (Address address: message.getFrom())
{
String from = address.toString();
int ltIndex = from.indexOf('<');
int atIndex = from.indexOf('@');
int gtIndex = from.indexOf('>');
if ((ltIndex!=-1) && (atIndex!=-1) && (gtIndex!=-1) && (ltIndex<atIndex) && (atIndex<gtIndex) )
{
username = from.substring(ltIndex+1, gtIndex);
userid = JID.escapeNode(username);
}
}
if (userid != null)
{
Log.info("processWorkgroupRequest userid " + userid);
String messageBody = null;
if (message.isMimeType("multipart/*"))
{
Multipart mp = (Multipart) message.getContent();
int count = mp.getCount();
for (int i = 0; i < count; i++)
{
Part part = mp.getBodyPart(i);
if (part.isMimeType("text/plain"))
{
messageBody = (String) part.getContent();
}
}
}
else
if (message.isMimeType("text/plain"))
{
messageBody = (String) message.getContent();
}
if (messageBody != null)
{
Log.info("processWorkgroupRequest body " + messageBody);
messageBody = messageBody.replace("\n", " ").replace("\r", "").replace("\t", "").replace("\"", "'");
String workgroupName = workgroupNodeName + "@workgroup." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
if (globalConnections.containsKey(userid) == false)
{
Log.info("processWorkgroupRequest smack session " + workgroupName);
ConnectionConfiguration config = new ConnectionConfiguration("localhost", 0);
XMPPConnection connection = new XMPPConnection(config);
connection.connect();
connection.loginAnonymousUser(userid);
globalConnections.put(userid, connection);
}
XMPPConnection globalConnection = globalConnections.get(userid);
Workgroup workgroup = getWorkgroup(workgroupName, globalConnection);
if (isOnline(workgroupName, globalConnection))
{
Map details = new HashMap();
details.put("username", fromUser == null ? username.split("@")[0] : fromUser.getName());
details.put("email", username);
details.put("question", messageBody);
if (workgroup != null) {
try {
workgroup.joinQueue(details, userid);
}
catch (XMPPException e) {
Log.error("Unable to join chat queue." + workgroupName, e);
sendWorkgroupEmail(JID.unescapeNode(userid), workgroupName, template, "");
}
}
} else {
Log.warn("processWorkgroupRequest workgroup is offline " + workgroupName);
sendWorkgroupEmail(JID.unescapeNode(userid), workgroupName, template, "");
}
} else {
Log.warn("processWorkgroupRequest email body is empty" + messageBody);
sendWorkgroupEmail(JID.unescapeNode(userid), workgroupNodeName, template, "");
}
} else {
Log.warn("processWorkgroupRequest bad email address " + message);
}
} catch (Exception e) {
Log.error("processWorkgroupRequest " + e);
}
}
private boolean isOnline(final String workgroupName, XMPPConnection globalConnection)
{
Presence presence = workgroupPresence.get(workgroupName);
if (presence == null)
{
Workgroup workgroup = getWorkgroup(workgroupName, globalConnection);
boolean isAvailable = workgroup.isAvailable();
presence = new Presence(isAvailable ? Presence.Type.available : Presence.Type.unavailable);
workgroupPresence.put(workgroupName, presence);
// Otherwise
PacketFilter fromFilter = new FromContainsFilter(workgroupName);
PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
PacketFilter andFilter = new AndFilter(fromFilter, presenceFilter);
globalConnection.addPacketListener(new PacketListener()
{
public void processPacket(Packet packet)
{
Presence presence = (Presence)packet;
workgroupPresence.put(workgroupName, presence);
}
}, andFilter);
return isAvailable;
}
return presence != null && presence.getType() == Presence.Type.available;
}
private Workgroup getWorkgroup(final String workgroupName, final XMPPConnection globalConnection)
{
Workgroup workgroup = (Workgroup)workgroups.get(workgroupName);
if (workgroup == null)
{
workgroup = new Workgroup(workgroupName, globalConnection);
workgroup.addInvitationListener(new WorkgroupInvitationListener()
{
public void invitationReceived(WorkgroupInvitation workgroupInvitation)
{
String room = workgroupInvitation.getGroupChatName().split("@")[0];
String userid = globalConnection.getUser();
String template = JiveGlobals.getProperty("ofmeet.email.workgroup.available.template", "Hello,\n\nAn Agent from [workgroup] is inviting you to a conference\n\nTo join, please click\n[videourl]\nFor audio only with no webcan, please click\n[audiourl]\n\nAdministrator - [domain]");
sendWorkgroupEmail(JID.unescapeNode(userid), workgroupName, template, room);
}
});
workgroups.put(workgroupName, workgroup);
}
return workgroup;
}
/*
BEGIN:VCALENDAR
METHOD:REQUEST
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
BEGIN:VTIMEZONE
TZID:GMT Standard Time
BEGIN:STANDARD
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T010000
TZOFFSETFROM:+0000
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
ORGANIZER;CN=Dele Olajide:MAILTO:yyyyyy@domain.com
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Dele:MAILT
O:xxxxx@domain.net
DESCRIPTION;LANGUAGE=en-US:Are you coming?\n
UID:2DB9FAF4-899F-440D-BC41-AFFDAAF2A3B4
SUMMARY;LANGUAGE=en-US:Movie tonight
DTSTART;TZID=GMT Standard Time:20151226T133000
DTEND;TZID=GMT Standard Time:20151226T140000
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20151226T131959Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION;LANGUAGE=en-US:Hoo\, Rochester
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-OWNERAPPTID:2113720871
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DISALLOW-COUNTER:FALSE
BEGIN:VALARM
DESCRIPTION:REMINDER
TRIGGER;RELATED=START:-PT15M
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR
*/
}