/**
* $RCSfile$
* $Revision$
* $Date: 2006-08-07 21:12:21 -0700 (Mon, 07 Aug 2006) $
*
* Copyright (C) 2004-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.xmpp.workgroup;
import java.net.URL;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import org.dom4j.Element;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.fastpath.util.TaskEngine;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.util.FastDateFormat;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.xmpp.workgroup.chatbot.Chatbot;
import org.jivesoftware.xmpp.workgroup.dispatcher.BasicDispatcherInfo;
import org.jivesoftware.xmpp.workgroup.dispatcher.DispatcherInfoProvider;
import org.jivesoftware.xmpp.workgroup.event.WorkgroupEventDispatcher;
import org.jivesoftware.xmpp.workgroup.interceptor.InterceptorManager;
import org.jivesoftware.xmpp.workgroup.interceptor.PacketRejectedException;
import org.jivesoftware.xmpp.workgroup.interceptor.RoomInterceptorManager;
import org.jivesoftware.xmpp.workgroup.interceptor.WorkgroupInterceptorManager;
import org.jivesoftware.xmpp.workgroup.request.InvitationRequest;
import org.jivesoftware.xmpp.workgroup.request.Request;
import org.jivesoftware.xmpp.workgroup.request.TransferRequest;
import org.jivesoftware.xmpp.workgroup.request.UserRequest;
import org.jivesoftware.xmpp.workgroup.routing.RoutingManager;
import org.jivesoftware.xmpp.workgroup.spi.BasicRequestFilterFactory;
import org.jivesoftware.xmpp.workgroup.spi.JiveLiveProperties;
import org.jivesoftware.xmpp.workgroup.spi.dispatcher.DbDispatcherInfoProvider;
import org.jivesoftware.xmpp.workgroup.utils.DbWorkgroup;
import org.jivesoftware.xmpp.workgroup.utils.FastpathConstants;
import org.jivesoftware.xmpp.workgroup.utils.ModelUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.component.ComponentManagerFactory;
import org.xmpp.muc.DestroyRoom;
import org.xmpp.muc.Invitation;
import org.xmpp.muc.JoinRoom;
import org.xmpp.muc.LeaveRoom;
import org.xmpp.muc.RoomConfiguration;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
/**
* <p>Database implementation of a workgroup agent.</p>
* <p>The current implementation doesn't store any meta data about an agent.</p>
*
* @author Derek DeMoro
*/
public class Workgroup {
private static final Logger Log = LoggerFactory.getLogger(Workgroup.class);
private static final String LOAD_WORKGROUP =
"SELECT jid, displayName, description, status, modes, creationDate, " +
"modificationDate, maxchats, minchats, offerTimeout, requestTimeout, " +
"schedule FROM fpWorkgroup WHERE workgroupID=?";
private static final String UPDATE_WORKGRUP =
"UPDATE fpWorkgroup SET displayName=?,description=?,status=?,modes=?," +
"creationDate=?,modificationDate=?,maxchats=?,minchats=?,offerTimeout=?," +
"requestTimeout=?,schedule=? WHERE workgroupID=?";
private static final String LOAD_QUEUES =
"SELECT queueID FROM fpQueue WHERE workgroupID=?";
private static final String CREATE_QUEUE =
"INSERT into fpQueue (queueID,workgroupID,name,priority,maxchats,minchats," +
"overflow,backupQueue) VALUES (?,?,?,?,?,?,0,0)";
private static final String DELETE_QUEUE =
"DELETE FROM fpQueue WHERE queueID=?";
private static final String DELETE_QUEUE_PROPS =
"DELETE FROM fpDispatcherProp WHERE ownerID=?";
private static final FastDateFormat UTC_FORMAT = FastDateFormat
.getInstance("yyyyMMdd'T'HH:mm:ss", TimeZone.getTimeZone("GMT+0"));
/**
* Flag indicating if the workgroup should accept new requests.
*/
private boolean open;
/**
* Flag indicating if the workgroup is following its schedule.
*/
private boolean followSchedule;
/**
* The schedule the workgroup follows when followSchedule is true.
*/
private Schedule schedule;
/**
* The description for the workgroup (used in the admin UI only - lazily loaded).
*/
private String description = null;
private Date creationDate;
private Date modDate;
private long offerTimeout = -1;
private long requestTimeout = -1;
private int maxChats;
private int minChats;
/**
* Convenience copy of the WorkgroupManager's agent manager (similar to deliverer convenience).
*/
private AgentManager agentManager;
/**
* Used when creating queues.
*/
private DispatcherInfoProvider dispatcherInfoProvider = new DbDispatcherInfoProvider();
/**
* Factory to produce request filters.
*/
private RequestFilterFactory requestFilterFactory = new BasicRequestFilterFactory();
/**
* Chatbot that will handle all the messages sent to this workgroup. If no chatbot was
* defined then messages will not be answered.
*/
private Chatbot chatbot;
// ------------------------------------------------------------------------
// Packet handlers
// ------------------------------------------------------------------------
private WorkgroupPresence workgroupPresenceHandler;
private WorkgroupIQHandler workgroupIqHandler;
private MessageHandler messageHandler;
private Map<Long, RequestQueue> queues = new HashMap<Long, RequestQueue>();
/**
* Custom properties for the workgroup.
*/
private JiveLiveProperties properties;
private String workgroupName;
private String displayName;
private long id;
/**
* Keep the conversation transcript of each room that this workgroup has created. Rooms are
* destroyed after the support session is over. Therefore, this variable keeps track of the
* chat transcripts of the current support sessions.
*/
private Map<String, Map<Packet, java.util.Date>> transcripts =
new ConcurrentHashMap<String, Map<Packet, java.util.Date>>();
/**
* Keep a counter of occupants for each room that the workgroup created. The
* occupants will be updated every time a presence packet is received from the
* room. When all the occupants (except the workgroup) has left the room then the
* workgroup will leave the room thus destroying it.
*/
private Map<String, Set<String>> occupantsCounter = new ConcurrentHashMap<String, Set<String>>();
/**
* Keep a list of the requests for which a room was created and still hasn't been destroyed.
* A new entry is added when sending room invitations and the same entry will be removed when
* the room is destroyed.
* Key: sessionID (ie. roomID), value: Request
*/
private Map<String, UserRequest> requests = new ConcurrentHashMap<String, UserRequest>();
public Workgroup(long id, AgentManager agentManager) {
this.id = id;
this.agentManager = agentManager;
// Initialize standalone runtime fields
// Initialize runtime fields that only save reference to the workgroup
workgroupPresenceHandler = new WorkgroupPresence(this);
workgroupIqHandler = new WorkgroupIQHandler();
workgroupIqHandler.setWorkgroup(this);
messageHandler = new MessageHandler(this);
// Load settings from database
loadWorkgroup();
loadQueues();
// Send presence to let everyone know you're available/unavailable
broadcastPresence();
broadcastQueuesStatus();
}
/**
* Broadcasts the presence of the workgroup to all users and agents of the workgroup.
*/
public void broadcastPresence() {
// TODO Send presence from the workgroup to the server
TaskEngine.getInstance().submit(new Runnable() {
public void run() {
try {
// Send the status of this workgroup to all the connected agents
Collection<AgentSession> sessions = getAgentSessions();
for (AgentSession session : sessions) {
workgroupPresenceHandler.sendPresence(session.getJID());
}
// Get all the users' JID in a Set since a user may be having a chat with
// many agents
Set<JID> jids = new HashSet<JID>();
for (AgentSession session : sessions) {
jids.addAll(session.getUsersJID(Workgroup.this));
}
// Send the status to all the users that are having a chat with agents of
// the workgroup
for (JID jid : jids) {
workgroupPresenceHandler.sendPresence(jid);
}
}
catch (Exception e) {
Log.error("Error broadcasting workgroup presence", e);
}
}
});
}
/**
* Broadcasts the presence of all the queues in the workgroup to all agents.
*/
public void broadcastQueuesStatus() {
TaskEngine.getInstance().submit(new Runnable() {
public void run() {
try {
// Send status of each queue to all the connected agents
for (RequestQueue requestQueue : getRequestQueues()) {
requestQueue.getAgentSessionList().broadcastQueueStatus(requestQueue);
}
}
catch (Exception e) {
Log.error("Error broadcasting status of queues", e);
}
}
});
}
/**
* Return true if the Workgroup is available to take requests.
*
* @return true if the workgroup is available to take requests.
*/
public boolean isAvailable() {
for (RequestQueue requestQueue : getRequestQueues()) {
Presence presence = requestQueue.getDetailedStatusPresence();
if (presence.getType() == null) {
return true;
}
}
return false;
}
// ###############################################################################
// Request queue management
// ###############################################################################
public RequestQueue createRequestQueue(String name) throws UnauthorizedException {
RequestQueue queue = null;
long queueID = SequenceManager.nextID(FastpathConstants.WORKGROUP_QUEUE);
// This should probably be moved into a queue manager class
// First create the queue, then the dispatcher,
// then the queue implementation object
boolean queueCreated = createQueue(queueID, name);
if (queueCreated) {
BasicDispatcherInfo info = new BasicDispatcherInfo(this, queueID,
"Round Robin Dispatcher", "None", -1, -1);
try {
dispatcherInfoProvider.insertDispatcherInfo(queueID, info);
queue = new RequestQueue(this, queueID);
}
catch (UserAlreadyExistsException e) {
Log.error(e.getMessage(), e);
}
}
else {
throw new UnauthorizedException();
}
queues.put(queueID, queue);
return queue;
}
public void deleteRequestQueue(RequestQueue queue) {
queues.remove(queue.getID());
// Delete the RequestQueue from the database
if (deleteQueue(queue.getID())) {
// Stop processing requests in this queue
queue.shutdown();
// Remove the agents from this queue
for (Agent agent : queue.getMembers()) {
queue.removeMember(agent);
}
// Remove the agent groups from this queue
for (Group group : queue.getGroups()) {
queue.removeGroup(group);
}
try {
// Delete the dispatcher of this queue from the database
dispatcherInfoProvider.deleteDispatcherInfo(queue.getID());
}
catch (UnauthorizedException e) {
Log.error(e.getMessage(), e);
}
}
}
/**
* Returns the Collection of RequestQueues ordered by ID.
*
* @return the collection of request queues.
*/
public Collection<RequestQueue> getRequestQueues() {
final List<RequestQueue> queueList = new ArrayList<RequestQueue>(queues.values());
// Sort by ID
Collections.sort(queueList, queueComparator);
return Collections.unmodifiableList(queueList);
}
public int getRequestQueueCount() {
return queues.size();
}
public RequestQueue getRequestQueue(long queueID) throws NotFoundException {
RequestQueue requestQueue = queues.get(queueID);
if (requestQueue == null) {
throw new NotFoundException("Queue not found for ID: " + queueID);
}
return requestQueue;
}
public RequestQueue getRequestQueue(String queueName) throws NotFoundException {
for (RequestQueue queue : queues.values()) {
if (queueName.equals(queue.getName())) {
return queue;
}
}
throw new NotFoundException("Queue not found for name: " + queueName);
}
/**
* Retuns the UserRequest that is associated to the support session being currently serviced or
* <tt>null</tt> if none was found.
*
* @param sessionID the ID of the support session.
* @return the UserRequest that is associated to the support session being currently serviced or
* null if none was found.
*/
public UserRequest getUserRequest(String sessionID) {
return requests.get(sessionID);
}
/**
* <p>Loads up a request queue from the database given the queueID.</p>
* <p>Used by the workgroup queue loading SQL code.</p>
*
* @param queueID The ID of the existing queue to load from the DB
* <p/>
* <!-- DbC -->
*/
private void loadRequestQueue(long queueID) {
queues.put(queueID, new RequestQueue(this, queueID));
}
/**
* Adds the request to a queue in the workgroup. If the workgroup is closed or the request
* does not pass a filter then the request will be rejected and this method will return false.
*
* @param request the request to add to a queue of this wokrgroup.
* @return true if the request was added to a queue.
*/
public boolean queueRequest(UserRequest request) {
// Retrieve routing manager
RoutingManager routingManager = RoutingManager.getInstance();
// Check if they require referer validation.
boolean contains = containsValidReferer(request);
if (!contains) {
return false;
}
if (getStatus() != Workgroup.Status.OPEN) {
return false;
}
// Check if the request may be accepted by the workgroup
PacketError.Condition error = requestFilterFactory.getFilter().filter(request);
if (error == null) {
synchronized (routingManager) {
// Add the request to the best queue of the workgroup
routingManager.routeRequest(this, request);
return true;
}
}
return false;
}
public void send(Packet packet) {
InterceptorManager interceptorManager = WorkgroupInterceptorManager.getInstance();
try {
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, false, false);
WorkgroupManager.getInstance().send(packet);
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, false, true);
}
catch (PacketRejectedException e) {
Log.warn("Packet was not sent " +
"due to interceptor REJECTION: " + packet.toXML(), e);
}
}
// ##############################################################################
// Packet handler methods - We pass through to specific packet handling classes
// ##############################################################################
public void process(Presence packet) {
workgroupPresenceHandler.process(packet);
}
public void process(IQ packet) {
workgroupIqHandler.process(packet);
}
public void process(Message packet) {
messageHandler.process(packet);
}
public void process(Packet packet) {
InterceptorManager interceptorManager = WorkgroupInterceptorManager.getInstance();
try {
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, true, false);
String mucDomain = WorkgroupManager.getInstance().getMUCServiceName();
if (mucDomain.equals(packet.getFrom().getDomain())) {
roomActivity(packet);
}
else if (packet instanceof Message) {
process((Message)packet);
}
else if (packet instanceof Presence) {
process((Presence)packet);
}
else if (packet instanceof IQ) {
process((IQ)packet);
}
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, true, true);
}
catch (PacketRejectedException e) {
rejectPacket(packet, e);
}
}
public void rejectPacket(Packet packet, PacketRejectedException e) {
if (packet instanceof IQ) {
IQ reply = new IQ();
reply.setChildElement(((IQ)packet).getChildElement().createCopy());
reply.setID(packet.getID());
reply.setTo(packet.getFrom());
reply.setFrom(packet.getTo());
reply.setError(PacketError.Condition.not_allowed);
send(reply);
}
else if (packet instanceof Presence) {
Presence reply = new Presence();
reply.setID(packet.getID());
reply.setTo(packet.getFrom());
reply.setFrom(packet.getTo());
reply.setError(PacketError.Condition.not_allowed);
send(reply);
}
// Check if a message notifying the rejection should be sent
if (e.getRejectionMessage() != null && e.getRejectionMessage().trim().length() > 0) {
// A message for the rejection will be sent to the sender of the rejected packet
Message notification = new Message();
notification.setTo(packet.getFrom());
notification.setFrom(packet.getTo());
notification.setBody(e.getRejectionMessage());
send(notification);
}
Log.warn("Packet was REJECTED " +
"by interceptor: " + packet.toXML(), e);
}
// ###############################################################################
// MUC related packets
// ###############################################################################
/**
* Notification message indicating that there has been new activity in a room. This implies
* that we need to update the conversation transcript of the group chat room and possibly
* update the number of occupants within the room.<p>
* <p/>
* If only the workgroup is present in the room then leave the room (i.e. destroying room) and
* proceed to save the room conversation transcript to the database.<p>
*
* @param packet the packet that was sent to the group chat room.
*/
private void roomActivity(Packet packet) {
// Skip packet sent from this workgroup in the room
if (packet.getFrom().toBareJID().equals(getGroupChatRoomName())) {
return;
}
RoomInterceptorManager interceptorManager = RoomInterceptorManager.getInstance();
String roomID = packet.getFrom().getNode();
// Get the sessionID
String sessionID = packet.getFrom().getNode();
synchronized (sessionID.intern()) {
if (packet instanceof Presence) {
Presence presence = (Presence)packet;
if (Presence.Type.error == presence.getType()) {
// A configuration must be wrong (eg. workgroup is not allowed to create rooms).
// Log the error presence
String warnMessage = "Possible server misconfiguration. Received error " +
"presence:" + presence.toXML();
Log.warn(warnMessage);
return;
}
// Get the JID of the presence's user
Element mucUser = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
// Skip this presence if no extended info was included in the presence
if (mucUser == null) {
return;
}
Element item = mucUser.element("item");
// Skip this presence if no item was included in the presence
if (item == null) {
return;
}
// Skip this presence if it's the presence of this workgroup in the room
if (workgroupName.equals(packet.getFrom().getResource())) {
return;
}
JID presenceFullJID = new JID(item.attributeValue("jid"));
String presenceJID = presenceFullJID.toBareJID();
// Invoke the room interceptor before processing the presence
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, false, false);
// Get the userID associated to this sessionID
UserRequest initialRequest = requests.get(sessionID);
// Add the new presence to the list of sent packets
Map<Packet, java.util.Date> messageList = transcripts.get(roomID);
if (messageList == null) {
messageList = new LinkedHashMap<Packet, java.util.Date>();
transcripts.put(roomID, messageList);
// Trigger the event that a chat support has started
WorkgroupEventDispatcher.chatSupportStarted(this, sessionID);
}
messageList.put(packet.createCopy(), new java.util.Date());
// Update the number of occupants in the room.
boolean occupantAdded = false;
Set<String> set = occupantsCounter.get(roomID);
if (set == null) {
set = new HashSet<String>();
occupantsCounter.put(roomID, set);
}
if (presence.isAvailable()) {
occupantAdded = set.add(presenceJID);
}
else {
String xpath = "/presence/*[name()='x']/*[name()='status']";
Element status = (Element)presence.getElement().selectSingleNode(xpath);
if (status == null || !"303".equals(status.attributeValue("code"))) {
// Remove the occupant unless the occupant is changing his nickname
set.remove(presenceJID);
}
}
// If the presence belongs to an Agent then create/update a track
// Look for an agent whose JID matches the presence's JID
String agentJID = null;
for (Agent agent : getAgents()) {
if (agent.getAgentJID().toBareJID().equals(presenceJID)) {
agentJID = agent.getAgentJID().toBareJID();
}
}
if (agentJID != null) {
AgentSession agentSession;
// Update the current chats that the agent is having
try {
agentSession = agentManager.getAgentSession(presenceFullJID);
if (agentSession != null) {
if (presence.isAvailable()) {
if (occupantAdded) {
agentSession.addChatInfo(this, sessionID, initialRequest, new java.util.Date());
// Trigger the event that an agent has joined a chat session
WorkgroupEventDispatcher.agentJoinedChatSupport(this, sessionID, agentSession);
}
}
else {
agentSession.removeChatInfo(this, sessionID);
// Trigger the event that an agent has left a chat session
WorkgroupEventDispatcher.agentLeftChatSupport(this, sessionID, agentSession);
}
}
}
catch (AgentNotFoundException e) {
// Do nothing since the AgentSession was not found
}
if (presence.isAvailable()) {
if (occupantAdded) {
// Store in the DB that an agent has joined a room
DbWorkgroup.updateJoinedSession(sessionID, agentJID, true);
}
}
else {
// Store in the DB that an agent has left a room
DbWorkgroup.updateJoinedSession(sessionID, agentJID, false);
}
}
else {
if (occupantAdded) {
// Notify the request that the user has joined a support session
initialRequest.supportStarted(roomID);
}
}
if (occupantAdded) {
initialRequest.userJoinedRoom(new JID(packet.getFrom().toBareJID()), presenceFullJID);
}
// If just the user has left the room, just persist the transcript
boolean isAgent = false;
try {
isAgent = agentManager.getAgentSession(presenceFullJID) != null;
}
catch (AgentNotFoundException e) {
// Ignore.
}
if (!((Presence)packet).isAvailable() && !isAgent) {
// Build the XML for the transcript
Map<Packet, java.util.Date> map = transcripts.get(roomID);
StringBuilder buf = new StringBuilder();
buf.append("<transcript>");
for (Packet p : map.keySet()) {
java.util.Date date = map.get(p);
// Add the delay information
if (p instanceof Message) {
Message storedMessage = (Message)p;
Element delay = storedMessage.addChildElement("x", "jabber:x:delay");
delay.addAttribute("stamp", UTC_FORMAT.format(date));
if (ModelUtil.hasLength(storedMessage.getBody())) {
buf.append(p.toXML());
}
}
else {
Presence storedPresence = (Presence)p;
Element delay = storedPresence.addChildElement("x", "jabber:x:delay");
delay.addAttribute("stamp", UTC_FORMAT.format(date));
buf.append(p.toXML());
}
// Append an XML representation of the packet to the string buffer
}
buf.append("</transcript>");
// Save the transcript (in XML) to the DB
DbWorkgroup.updateTranscript(sessionID, buf.toString(), new java.util.Date());
}
// If the agent and the user left the room then proceed to dump the transcript to
// the DB and destroy the room
if (!((Presence)packet).isAvailable() && set.isEmpty()) {
// Delete the counter of occupants for this room
occupantsCounter.remove(roomID);
initialRequest = requests.remove(sessionID);
if (initialRequest != null && initialRequest.hasJoinedRoom()) {
// Notify the request that the support session has finished
initialRequest.supportEnded();
}
// Build the XML for the transcript
Map<Packet, java.util.Date> map = transcripts.get(roomID);
StringBuilder buf = new StringBuilder();
buf.append("<transcript>");
for (Packet p : map.keySet()) {
java.util.Date date = map.get(p);
// Add the delay information
if (p instanceof Message) {
Message storedMessage = (Message)p;
Element delay = storedMessage.addChildElement("x", "jabber:x:delay");
delay.addAttribute("stamp", UTC_FORMAT.format(date));
if (ModelUtil.hasLength(storedMessage.getBody())) {
buf.append(p.toXML());
}
}
else {
Presence storedPresence = (Presence)p;
Element delay = storedPresence.addChildElement("x", "jabber:x:delay");
delay.addAttribute("stamp", UTC_FORMAT.format(date));
buf.append(p.toXML());
}
// Append an XML representation of the packet to the string buffer
}
buf.append("</transcript>");
// Save the transcript (in XML) to the DB
//DbWorkgroup.updateTranscript(sessionID, buf.toString(), new java.util.Date());
// Leave Chat Room (the room will be destroyed)
String roomJID = packet.getFrom().toString() + "/" + getJID().getNode();
LeaveRoom leaveRoom = new LeaveRoom(getFullJID().toString(), roomJID);
send(leaveRoom);
// Remove the transcript information of this room since the room no
// longer exists
transcripts.remove(roomID);
// Trigger the event that a chat support has finished
WorkgroupEventDispatcher.chatSupportFinished(this, sessionID);
}
// Invoke the room interceptor after the presence has been processed
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, false, true);
}
else if (packet instanceof Message) {
// Filter messages sent from the room itself since we don't want the
// transcript to include things like "room locked"
if (packet.getFrom().getResource() != null) {
// Invoke the room interceptor before processing the presence
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, false, false);
// Add the new message to the list of sent packets
Map<Packet, java.util.Date> messageList = transcripts.get(roomID);
if (messageList == null) {
messageList = new LinkedHashMap<Packet, java.util.Date>();
transcripts.put(roomID, messageList);
}
messageList.put(packet.createCopy(), new java.util.Date());
// Invoke the room interceptor after the presence has been processed
interceptorManager.invokeInterceptors(getJID().toBareJID(), packet, false, true);
}
}
}
}
/**
* An agent has accepted the offer and was choosen to answer the user's requests. The workgroup
* will create a new room where the agent can answer the user's needs. Once the room has been
* created, the Agent and the user that made the request will receive invitiations to join the
* newly created room.<p>
* <p/>
* The workgroup will listen for all the packets sent to the room and generate a conversation
* transcript.
*
* @param agent the AgentSession that accepted and was choosen to respond the user's requests.
* @param request the request made by a user.
*/
public void sendInvitation(AgentSession agent, UserRequest request) {
// TODO When running LA as a plugin (internal component) and if the plugin is removed then
// we need to destroy all MUC rooms created by workgroups
try {
RoomInterceptorManager interceptorManager = RoomInterceptorManager.getInstance();
WorkgroupManager workgroupManager = WorkgroupManager.getInstance();
String userJID = request.getUserJID().toString();
final Workgroup sessionWorkgroup = request.getWorkgroup();
final String sessionID = request.getSessionID();
String workgroupName = getJID().getNode();
final String serviceName = workgroupManager.getMUCServiceName();
final String roomName = sessionID + "@" + serviceName;
final String roomJID = roomName + "/" + workgroupName;
// Create the room by joining it. The workgroup will be the owner of the room and will
// invite the Agent and the user to join the room
JoinRoom joinRoom = new JoinRoom(getFullJID().toString(), roomJID);
interceptorManager.invokeInterceptors(getJID().toBareJID(), joinRoom, false, false);
send(joinRoom);
interceptorManager.invokeInterceptors(getJID().toBareJID(), joinRoom, false, true);
// Configure the newly created room
Map<String, Collection<String>> fields = new HashMap<String, Collection<String>>();
// Make a non-public room
List<String> values = new ArrayList<String>();
values.add("0");
fields.put("muc#roomconfig_publicroom", values);
// Set the room description
values = new ArrayList<String>();
values.add(roomName);
fields.put("muc#roomconfig_roomdesc", values);
// Set that anyone can change the room subject
values = new ArrayList<String>();
values.add("1");
fields.put("muc#roomconfig_changesubject", values);
// Make the room temporary
values = new ArrayList<String>();
values.add("0");
fields.put("muc#roomconfig_persistentroom", values);
// Set that only moderators can see the occupants' JID
values = new ArrayList<String>();
values.add("moderators");
fields.put("muc#roomconfig_whois", values);
// Set that we want packets to include the real JID
values = new ArrayList<String>();
values.add("0");
fields.put("anonymous", values);
// Only broadcast presences of participants and visitors
values = new ArrayList<String>();
values.add("participant");
values.add("visitor");
fields.put("muc#roomconfig_presencebroadcast", values);
RoomConfiguration conf = new RoomConfiguration(fields);
conf.setTo(roomName);
conf.setFrom(getFullJID());
interceptorManager.invokeInterceptors(getJID().toBareJID(), conf, false, false);
send(conf);
interceptorManager.invokeInterceptors(getJID().toBareJID(), conf, false, true);
// Create a new entry for the active session and the request made by the user
requests.put(sessionID, request);
// Invite the Agent to the new room
Invitation invitation = new Invitation(agent.getJID().toString(), sessionID);
invitation.setTo(roomName);
invitation.setFrom(getFullJID());
// Add workgroup extension that includes the JID of the user that made the request
Element element = invitation.addChildElement("offer", "http://jabber.org/protocol/workgroup");
element.addAttribute("jid", userJID);
// Add custom extension that includes the sessionID
element = invitation.addChildElement("session", "http://jivesoftware.com/protocol/workgroup");
element.addAttribute("workgroup", sessionWorkgroup.getJID().toString());
element.addAttribute("id", sessionID);
// Add custom extension that includes the userID if the session belongs to an
// anonymous user
if (request.isAnonymousUser()) {
element = invitation.addChildElement("user", "http://jivesoftware.com/protocol/workgroup");
element.addAttribute("id", request.getUserID());
}
interceptorManager.invokeInterceptors(getJID().toBareJID(), invitation, false, false);
send(invitation);
interceptorManager.invokeInterceptors(getJID().toBareJID(), invitation, false, true);
// Invite the user to the new room
sendUserInvitiation(request, roomName);
// Notify the request that invitations for support have been sent
request.invitationsSent(sessionID);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
/**
* Sends the room invitation to the user that made the request.
*
* @param request the Request that the user made to join a workgroup.
* @param roomID the id of the room where the user is being invited.
*/
public void sendUserInvitiation(UserRequest request, String roomID) {
String userJID = request.getUserJID().toString();
final String sessionID = request.getSessionID();
final String serviceName = WorkgroupManager.getInstance().getMUCServiceName();
final String roomName = sessionID + "@" + serviceName;
Invitation invitation = new Invitation(userJID, "Please join me for a chat.");
invitation.setTo(roomName);
invitation.setFrom(getFullJID());
// Add workgroup extension that includes the JID of the workgroup
Element element = invitation.addChildElement("workgroup",
"http://jabber.org/protocol/workgroup");
element.addAttribute("jid", getJID().toBareJID());
// Add custom extension that includes the sessionID
element =
invitation.addChildElement("session", "http://jivesoftware.com/protocol/workgroup");
element.addAttribute("id", sessionID);
RoomInterceptorManager interceptorManager = RoomInterceptorManager.getInstance();
interceptorManager.invokeInterceptors(getJID().toBareJID(), invitation, false, false);
send(invitation);
interceptorManager.invokeInterceptors(getJID().toBareJID(), invitation, false, true);
}
/**
* Users that have received an invitation to join a room and haven't done so may receive
* another invitation. The exact (recovery) action to do will depend on the type of client
* used by the user to join the workgroup. For instance, if the user was using a chatbot to
* join the workgroup then instead of receiving another invitation he may be asked if he wants
* to receive another invitation.
*/
public void checkRequests() {
for (String roomID : requests.keySet()) {
UserRequest request = requests.get(roomID);
// Check invitations if an invitation was sent and the user hasn't joined the room yet
if (request != null) {
request.checkRequest(roomID);
}
}
}
/**
* Returns the list of packets, including Presence and Messages, sent to the room or
* <tt>null</tt> if the room has currently no occupants (ie. does not exist).
*
* @param roomID the id of a room (node of the JID) that exists and was created by this
* workgroup for a chat with a user
* @return the list of packets sent to an existing room
*/
public Map<Packet, java.util.Date> getTranscript(String roomID) {
return transcripts.get(roomID);
}
// #############################################################################
// Package access methods - For classes that need extra workgroup access
// #############################################################################
/**
* <p>Obtain the agent manager associated with the workgroup.</p>
*
* @return The agent manager for this workgroup
* <p/>
* <!-- DbC -->
*/
public AgentManager getAgentManager() {
return agentManager;
}
/**
* Returns a collection with all the agent session that are present in the workgroup. If the
* same agent is present in more than one queue then the answer will only include one instance
* of the agent session.
*
* @return a collection with all the agent session that are present in the workgroup.
*/
public Collection<AgentSession> getAgentSessions() {
Collection<AgentSession> answer = new HashSet<AgentSession>();
for (RequestQueue queue : queues.values()) {
answer.addAll(queue.getAgentSessionList().getAgentSessions());
}
return Collections.unmodifiableCollection(answer);
}
/**
* Returns a collection with all the agent session that are available for chat in the workgroup.
* A chat session is available for chat based on the presence status. If the same agent is
* present in more than one queue then the answer will only include one instance of the agent
* session.
*
* @return a collection with all the agent session that are available for chat in the workgroup.
*/
public Collection<AgentSession> getAgentAvailableSessions() {
Collection<AgentSession> answer = new HashSet<AgentSession>();
for (RequestQueue queue : queues.values()) {
for (AgentSession session : queue.getAgentSessionList().getAgentSessions()) {
if (session.isAvailableToChat()) {
answer.add(session);
}
}
}
return Collections.unmodifiableCollection(answer);
}
/**
* Returns a collection with all the agents that belong to the workgroup. If the same agent
* is present in more than one queue then the answer will only include one instance
* of the agent.
*
* @return a collection with all the agents that are belong to the workgroup.
*/
public Collection<Agent> getAgents() {
Collection<Agent> answer = new HashSet<Agent>();
for (RequestQueue queue : queues.values()) {
answer.addAll(queue.getMembers());
for (Group group : queue.getGroups()) {
for (Agent agent : agentManager.getAgents(group)) {
answer.add(agent);
}
}
}
return Collections.unmodifiableCollection(answer);
}
// #############################################################################
// Field access methods
// #############################################################################
public void setDescription(String description) {
if (description == null) {
description = "";
}
if (description.equals(this.description)) {
// Do nothing
return;
}
this.description = description;
updateWorkgroup();
}
public String getDescription() {
return description;
}
/**
* Returns the chatbot that will respond to messages sent to this workgroup. Workgroups may
* have a chatbot but it's not mandatory to have one.
*
* @return the chatbot that will respond to messages sent to this workgroup.
*/
public Chatbot getChatBot() {
if (!isChatbotEnabled()) {
return null;
}
if (chatbot == null) {
synchronized (this) {
if (chatbot == null) {
chatbot = new Chatbot(this);
}
}
}
return chatbot;
}
/**
* Sets if the workgroup should use a chatbot for answering the messages sent to the workgroup.
*
* @param enabled true if a chatbot will respond to the messages sent to the workgroup.
* @throws UnauthorizedException if not allowed to change the workgroup property
*/
public void chatbotEnabled(boolean enabled) throws UnauthorizedException {
getProperties().setProperty("chatbot.enabled", enabled ? "true" : "false");
}
/**
* Returns true if the chatbot is enabled. When the chatbot is enabled it will answer
* messages sent to the workgroup.
*
* @return true if the chatbot is enabled.
*/
public boolean isChatbotEnabled() {
return "true".equals(getProperties().getProperty("chatbot.enabled"));
}
public Status getStatus() {
// TODO: The logic in this method appears too complex. May need refactor after
// TODO: removing schedule feature.
boolean actualOpenStatus = open;
// Workgroup can only be open if there are agents in the workgroup.
if (actualOpenStatus) {
actualOpenStatus = isOpen();
if (open) {
if (actualOpenStatus) {
return Status.OPEN;
}
else {
return Status.READY;
}
}
else {
return Status.CLOSED;
}
}
return Status.CLOSED;
}
private boolean isOpen() {
boolean opened = false;
for (RequestQueue requestQueue : getRequestQueues()) {
opened = requestQueue.getAgentSessionList().containsAvailableAgents();
if (opened) {
break;
}
}
return opened;
}
public void setStatus(Status status) {
if (status == Status.OPEN || status == Status.READY) {
if (open) {
// Do nothing if the value is not going to change
return;
}
this.open = true;
}
else {
if (!open) {
// Do nothing if the value is not going to change
return;
}
this.open = false;
}
// Seems that this method is being used as an initialization resort so we are resetting
// the schedule of the workgroup (if there was one)
disableSchedule();
if (updateWorkgroup()) {
broadcastPresence();
}
}
/**
* Notification message that the some images of the workgroup has changed.
*/
public void imagesChanged() {
// Note: The update to the DB is useless except for updating the last modified date though
// the actual modification took place in another table. But we could say that images are
// an internal component of this object. :) The last modified date will be sent in the
// broadcasted presence
if (updateWorkgroup()) {
broadcastPresence();
}
}
public boolean isFollowingSchedule() {
return false;
}
/**
* Disables the schedule that this workgroup might be following. To enable the schedule a new
* schedule must be assigned to the workgroup.
*/
public void disableSchedule() {
followSchedule = false;
if (schedule != null) {
schedule.clear();
}
if (updateWorkgroup()) {
broadcastPresence();
}
}
public Schedule getSchedule() {
return schedule;
}
/**
* Sets a new schedule for this workgroup thus enabling the scheduling feature.
*
* @param schedule the new schedule to follow for this workgroup.
*/
public void setSchedule(Schedule schedule) {
if (schedule == null || schedule.getID() != id) {
throw new IllegalArgumentException();
}
followSchedule = true;
if (updateWorkgroup()) {
broadcastPresence();
}
}
public int getMaxChats() {
if (isDefaultMaxChats()) {
return WorkgroupManager.getInstance().getDefaultMaxChats();
}
return maxChats;
}
public void setMaxChats(int max) {
if (max == maxChats) {
// Do nothing
return;
}
maxChats = max;
updateWorkgroup();
}
public int getMinChats() {
if (isDefaultMinChats()) {
return WorkgroupManager.getInstance().getDefaultMinChats();
}
return minChats;
}
public void setMinChats(int min) {
if (min == minChats) {
// Do nothing
return;
}
minChats = min;
updateWorkgroup();
}
public void setRequestTimeout(long timeout) {
if (timeout == requestTimeout) {
// Do nothing
return;
}
requestTimeout = timeout;
updateWorkgroup();
}
public long getRequestTimeout() {
if (isDefaultRequestTimeout()) {
return WorkgroupManager.getInstance().getDefaultRequestTimeout();
}
return requestTimeout;
}
public void setOfferTimeout(long timeout) {
if (timeout == offerTimeout) {
// Do nothing
return;
}
offerTimeout = timeout;
updateWorkgroup();
}
public long getOfferTimeout() {
if (isDefaultOfferTimeout()) {
return WorkgroupManager.getInstance().getDefaultOfferTimeout();
}
return offerTimeout;
}
public boolean isDefaultMaxChats() {
return maxChats == -1;
}
public boolean isDefaultMinChats() {
return minChats == -1;
}
public boolean isDefaultRequestTimeout() {
return requestTimeout == -1;
}
public boolean isDefaultOfferTimeout() {
return offerTimeout == -1;
}
public DbProperties getProperties() {
if (properties == null) {
properties = new JiveLiveProperties("fpWorkgroupProp", id);
}
return properties;
}
public void shutdown() {
for (RequestQueue requestQueue : getRequestQueues()) {
requestQueue.shutdown();
}
// Release the chatbot
chatbot = null;
queueComparator = null;
}
private void loadWorkgroup() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_WORKGROUP);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
workgroupName = rs.getString(1);
if (rs.getString(2) != null && rs.getString(2).length() > 0) {
displayName = rs.getString(2);
}
else {
displayName = workgroupName;
}
description = rs.getString(3);
open = rs.getInt(4) == 1;
followSchedule = rs.getInt(5) == 1;
creationDate = new Date(Long.parseLong(rs.getString(6).trim()));
modDate = new Date(Long.parseLong(rs.getString(7).trim()));
maxChats = rs.getInt(8);
minChats = rs.getInt(9);
offerTimeout = rs.getInt(10);
requestTimeout = rs.getInt(11);
schedule = new Schedule(id, rs.getString(12));
}
}
catch (SQLException ex) {
Log.error(ex.getMessage(), ex);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
private void loadQueues() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_QUEUES);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
while (rs.next()) {
loadRequestQueue(rs.getLong(1));
}
}
catch (SQLException ex) {
Log.error(ex.getMessage(), ex);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
private boolean createQueue(long handbackid, Object data) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(CREATE_QUEUE);
pstmt.setLong(1, handbackid);
pstmt.setLong(2, id);
pstmt.setString(3, (String)data);
pstmt.setInt(4, 0);
pstmt.setInt(5, -1);
pstmt.setInt(6, -1);
pstmt.executeUpdate();
return true;
}
catch (SQLException ex) {
Log.error(ex.getMessage(), ex);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return false;
}
private boolean deleteQueue(long handbackid) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_QUEUE);
pstmt.setLong(1, handbackid);
pstmt.executeUpdate();
pstmt.close();
// Delete dispatcher properties
pstmt = con.prepareStatement(DELETE_QUEUE_PROPS);
pstmt.setLong(1, handbackid);
pstmt.executeUpdate();
return true;
}
catch (SQLException ex) {
Log.error(ex.getMessage(), ex);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return false;
}
private boolean updateWorkgroup() {
// Update the last modification date
modDate = new Date(System.currentTimeMillis());
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_WORKGRUP);
pstmt.setString(1, displayName);
pstmt.setString(2, description);
pstmt.setInt(3, open ? 1 : 0);
pstmt.setInt(4, followSchedule ? 1 : 0);
pstmt.setString(5, StringUtils.dateToMillis(creationDate));
pstmt.setString(6, StringUtils.dateToMillis(modDate));
pstmt.setInt(7, maxChats);
pstmt.setInt(8, minChats);
pstmt.setLong(9, offerTimeout);
pstmt.setLong(10, requestTimeout);
pstmt.setString(11, schedule.toString());
pstmt.setLong(12, id);
pstmt.executeUpdate();
return true;
}
catch (SQLException ex) {
Log.error(ex.getMessage(), ex);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return false;
}
/**
* Returns the JID of the workgroup. The node of the JID will be the workgroup name and
* the domain will have a "workgroup." prefix before the hostname of the server. No
* resource is included in the returned JID.
*
* @return the JID of the workgroup.
*/
public JID getJID() {
return new JID(workgroupName + "@workgroup." +
ComponentManagerFactory.getComponentManager().getServerName());
}
/**
* Returns a full JID of the workgroup composed by the workgroup JID and a resource that
* will match the workgroup name. The only place where a full JID may be required is when
* sending packets to the conference service. Somce servers require that a full JID might
* be used to route packets between components.
*
* @return a full JID of the workgroup composed by the workgroup JID and a resource that
* will match the workgroup name.
*/
public JID getFullJID() {
return new JID(workgroupName,
"workgroup." + ComponentManagerFactory.getComponentManager().getServerName(),
workgroupName);
}
public long getID() {
return id;
}
/**
* Returns the name to use when displaying the workgroup. This information can be used
* when showing the workgroup as a roster item.
*
* @return the name to use when displaying the workgroup.
*/
public String getDisplayName() {
return displayName;
}
/**
* Sets the name to use when displaying the workgroup. This information can be used
* when showing the workgroup as a roster item.
*
* @param displayName the name to use when displaying the workgroup.
*/
public void setDisplayName(String displayName) {
if (displayName.equals(this.displayName)) {
// Do nothing
return;
}
this.displayName = displayName;
updateWorkgroup();
}
/**
* Returns the last date when the workgroup properties were modified.
*
* @return the last date when the workgroup properties were modified.
*/
public Date getModificationDate() {
return modDate;
}
/**
* Notification message that a new AgentSession has been started. This method is useful
* for triggering events (in the future).
*
* @param agentSession the session that has just been started.
*/
public void agentJoined(AgentSession agentSession) {
// Since the session has received the queues status details we need to update the status
// in the manager so that the thread that is polling for changes in the status does not
// send the queues status details again to this session
WorkgroupManager.getInstance().updateWorkgroupStatus(this);
// Trigger the event that an agent has joined the workgroup
WorkgroupEventDispatcher.agentJoined(this, agentSession);
}
/**
* Notification message that an AgentSession has ended. This method is useful
* for triggering events (in the future).
*
* @param agentSession the session that has ended.
*/
public void agentDeparted(AgentSession agentSession) {
// Trigger the event that an agent has left the workgroup
WorkgroupEventDispatcher.agentDeparted(this, agentSession);
// Update the status in the manager so that the thread that is polling for changes in
// the status does not send the queues status details again to this session
WorkgroupManager.getInstance().updateWorkgroupStatus(this);
}
/**
* Notification method saying that the workgroup has been opened.
*/
public void notifyOpened() {
// Notify the prensence handler of this workgroup that the workgroup is now opened. The
// presence handler will notify the availability of the workgroup to the users that are
// tracking the workgroup's presence
workgroupPresenceHandler.broadcastWorkgroupPresence();
// Trigger the event that the workgroup has been opened
WorkgroupEventDispatcher.workgroupOpened(this);
}
/**
* Notification method saying that the workgroup has been closed.
*/
public void notifyClosed() {
// Notify the prensence handler of this workgroup that the workgroup is now closed. The
// presence handler will notify the availability of the workgroup to the users that are
// tracking the workgroup's presence
workgroupPresenceHandler.broadcastWorkgroupPresence();
// Trigger the event that the workgroup has been closed
WorkgroupEventDispatcher.workgroupClosed(this);
}
public void cleanup() {
// TODO Clean up dangling requests
// TODO Destroy rooms that never got occupants except the workgroup
// Clean up the chatbot sessions
if (chatbot != null) {
chatbot.cleanup();
}
}
/**
* Sends information to the agent that requested it about the occupants in the specified
* room. If the room does no longer exist then no information will be returned. This means
* that the chat should be happening at the moment of the query.
*
* @param packet the request sent by the agent.
* @param roomID the id of the room that the agent is requesting information
*/
public void sendOccupantsInfo(IQ packet, String roomID) {
IQ statusPacket = IQ.createResultIQ(packet);
Element occupantsInfo = statusPacket.setChildElement("occupants-info",
"http://jivesoftware.com/protocol/workgroup");
occupantsInfo.addAttribute("roomID", roomID);
Map<Packet, java.util.Date> packets = transcripts.get(roomID);
if (packets != null) {
Collection<String> processed = new ArrayList<String>();
for (Packet p : packets.keySet()) {
if (p instanceof Presence) {
Presence presence = (Presence)p;
// Get the JID of the presence's user
String userJID = presence.getChildElement("x",
"http://jabber.org/protocol/muc#user")
.element("item")
.attributeValue("jid");
// Only add information about the first presence so we know the time when the
// occupant joined the room
if (!processed.contains(userJID)) {
processed.add(userJID);
Element occupantInfo = occupantsInfo.addElement("occupant");
occupantInfo.addElement("jid").setText(userJID);
occupantInfo.addElement("nickname").setText(presence.getFrom().getResource());
occupantInfo.addElement("joined").setText(UTC_FORMAT.format(packets.get(p)));
}
}
}
}
// Send the response
send(statusPacket);
}
public void processInvitation(InvitationRequest invitation, IQ packet) {
IQ reply = IQ.createResultIQ(packet);
reply.setFrom(getJID());
// Verify that requester is a valid agent
AgentSession agentSession = null;
try {
agentSession = agentManager.getAgentSession(packet.getFrom());
} catch (AgentNotFoundException e) {
// Ignore
}
if (agentSession == null) {
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(new PacketError(PacketError.Condition.item_not_found));
send(reply);
Log.debug("Agent not found while accepting offer");
return;
}
// Answer that the invitation was received and that it is being processed
send(reply);
// Execute the invitation
invitation.execute();
}
public void processTransfer(TransferRequest transfer, IQ packet) {
IQ reply = IQ.createResultIQ(packet);
reply.setFrom(getJID());
// Verify that requester is a valid agent
AgentSession agentSession = null;
try {
agentSession = agentManager.getAgentSession(packet.getFrom());
} catch (AgentNotFoundException e) {
// Ignore
}
if (agentSession == null) {
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(new PacketError(PacketError.Condition.item_not_found));
send(reply);
Log.debug("Agent not found while accepting offer");
return;
}
// Answer that the transfer was received and that it is being processed
send(reply);
// Execute the transfer
transfer.execute();
}
void createGroupChatRoom() {
String roomJID = getGroupChatRoomName() + "/workgroup";
// Create the room by joining it. The workgroup will be the owner of the room and will
// invite the Agent and the user to join the room
JoinRoom joinRoom = new JoinRoom(getFullJID().toString(), roomJID);
send(joinRoom);
// Configure the newly created room
Map<String, Collection<String>> fields = new HashMap<String, Collection<String>>();
// Make a non-public room
List<String> values = new ArrayList<String>();
values.add("0");
fields.put("muc#roomconfig_publicroom", values);
// Set the room name
values = new ArrayList<String>();
values.add("Workgroup " + getJID().getNode() + " Chat Room");
fields.put("muc#roomconfig_roomname", values);
// Set the room description
values = new ArrayList<String>();
values.add("Workgroup Chat Room");
fields.put("muc#roomconfig_roomdesc", values);
// Set the max number of occupants to unlimited
values = new ArrayList<String>();
values.add("0");
fields.put("muc#roomconfig_maxusers", values);
// Set that anyone can change the room subject
values = new ArrayList<String>();
values.add("1");
fields.put("muc#roomconfig_changesubject", values);
// Make the room persistent
values = new ArrayList<String>();
values.add("1");
fields.put("muc#roomconfig_persistentroom", values);
// Make the room not moderated
values = new ArrayList<String>();
values.add("0");
fields.put("muc#roomconfig_moderatedroom", values);
// Make the room not members-only
values = new ArrayList<String>();
values.add("0");
fields.put("muc#roomconfig_membersonly", values);
// Set that anyone can send invitations
values = new ArrayList<String>();
values.add("1");
fields.put("muc#roomconfig_allowinvites", values);
// Make the room not password protected
values = new ArrayList<String>();
values.add("0");
fields.put("muc#roomconfig_passwordprotectedroom", values);
// Enable the log for the room
values = new ArrayList<String>();
values.add("1");
fields.put("muc#roomconfig_enablelogging", values);
// Set that only moderators can see the occupants' JID
values = new ArrayList<String>();
values.add("moderators");
fields.put("muc#roomconfig_whois", values);
// Only broadcast presences of participants and visitors
values = new ArrayList<String>();
values.add("moderator");
values.add("participant");
values.add("visitor");
fields.put("muc#roomconfig_presencebroadcast", values);
RoomConfiguration conf = new RoomConfiguration(fields);
conf.setTo(getGroupChatRoomName());
conf.setFrom(getFullJID());
send(conf);
// Change the subject of the room by sending a new message
Message message = new Message();
message.setType(Message.Type.groupchat);
message.setSubject("This is a private discussion room for members of this workgroup.");
message.setFrom(getFullJID());
message.setTo(getGroupChatRoomName());
send(message);
// Leave Chat Room
LeaveRoom leaveRoom = new LeaveRoom(getFullJID().toString(), roomJID);
send(leaveRoom);
}
/**
* The workgroup is been deleted so destroy the room and remove the accepted
* presence subscriptions.
*/
void destroy() {
// Notify the handler of the accepted presence subscriptions that the workgroup is been
// destroyed
workgroupPresenceHandler.workgroupDestroyed();
// Destroy the group chat room of this workgroup
destroyGroupChatRoom();
}
private void destroyGroupChatRoom() {
String roomJID = getGroupChatRoomName() + "/workgroup";
// We need to be an occupant of the room to destroy it
JoinRoom joinRoom = new JoinRoom(getFullJID().toString(), roomJID);
send(joinRoom);
// Destroy the group chat room of the workgroup
DestroyRoom destroy = new DestroyRoom(null, null);
destroy.setFrom(getFullJID());
destroy.setTo(getGroupChatRoomName());
send(destroy);
}
private String getGroupChatRoomName() {
String serviceName = WorkgroupManager.getInstance().getMUCServiceName();
return "workgroup-" + this.getJID().getNode() + "@" + serviceName;
}
private boolean containsValidReferer(Request request) {
try {
// Check for valid domains
final DbProperties props = getProperties();
String validDomains = props.getProperty("validDomains");
// If there are valid domains specified, then validate
if (ModelUtil.hasLength(validDomains)) {
Map<String, List<String>> metadata = request.getMetaData();
List<String> list = metadata.get("referer");
if (metadata.containsKey("referer")) {
metadata.remove("referer");
}
if (list != null && list.size() > 0) {
String referer = list.get(0);
URL refererURL = new URL(referer);
String domain = refererURL.getHost().toLowerCase();
StringTokenizer tkn = new StringTokenizer(validDomains, ",");
boolean match = false;
while (tkn.hasMoreTokens()) {
String token = tkn.nextToken().trim().toLowerCase();
if (domain.endsWith(token)) {
match = true;
break;
}
}
return match;
}
else {
return false;
}
}
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
return true;
}
public WorkgroupPresence getWorkgroupPresenceHandler(){
return workgroupPresenceHandler;
}
/**
* Sorts all <code>RequestQueue</code> by ID.
*/
static Comparator<RequestQueue> queueComparator = new Comparator<RequestQueue>() {
public int compare(RequestQueue queue1, RequestQueue queue2) {
float int1 = queue1.getID();
float int2 = queue2.getID();
if (int1 == int2) {
return 0;
}
if (int1 > int2) {
return 1;
}
if (int1 < int2) {
return -1;
}
return 0;
}
};
public static enum Status {
CLOSED,
READY,
OPEN
}
}