/* * 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.io.IOException; import java.sql.Connection; 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.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.dom4j.Element; import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.SequenceManager; import org.jivesoftware.openfire.PresenceManager; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.commands.AdHocCommandManager; import org.jivesoftware.openfire.event.GroupEventDispatcher; import org.jivesoftware.openfire.event.GroupEventListener; import org.jivesoftware.openfire.fastpath.commands.CreateWorkgroup; import org.jivesoftware.openfire.fastpath.commands.DeleteWorkgroup; import org.jivesoftware.openfire.fastpath.events.EmailTranscriptEvent; import org.jivesoftware.openfire.fastpath.settings.chat.ChatSettingsManager; import org.jivesoftware.openfire.fastpath.util.TaskEngine; import org.jivesoftware.openfire.fastpath.util.WorkgroupUtils; import org.jivesoftware.openfire.group.Group; import org.jivesoftware.openfire.net.SASLAuthentication; import org.jivesoftware.openfire.sasl.JiveSharedSecretSaslServer; import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.StringUtils; import org.jivesoftware.xmpp.workgroup.disco.IQDiscoInfoHandler; import org.jivesoftware.xmpp.workgroup.disco.IQDiscoItemsHandler; import org.jivesoftware.xmpp.workgroup.event.WorkgroupEventDispatcher; import org.jivesoftware.xmpp.workgroup.routing.RoutingManager; import org.jivesoftware.xmpp.workgroup.search.ChatSearchManager; import org.jivesoftware.xmpp.workgroup.search.IQChatSearchHandler; import org.jivesoftware.xmpp.workgroup.utils.FastpathConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.component.Component; import org.xmpp.component.ComponentException; import org.xmpp.component.ComponentManager; import org.xmpp.component.ComponentManagerFactory; 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; /** * Manages workgroups in the system. This manager primarily defers to the workgroups * to manage themselves and serves as a 'factory' for creating, obtaining and deleting them. * * @author Derek DeMoro */ public class WorkgroupManager implements Component { private static final Logger Log = LoggerFactory.getLogger(WorkgroupManager.class); private static final String LOAD_WORKGROUPS = "SELECT workgroupID FROM fpWorkgroup"; private static final String ADD_WORKGROUP = "INSERT INTO fpWorkgroup (workgroupID, jid, displayName, description, status, " + "creationDate, modificationDate, maxchats, minchats, offerTimeout, requestTimeout, " + "modes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"; private static final String DELETE_WORKGROUP = "DELETE FROM fpWorkgroup WHERE workgroupID=?"; private static WorkgroupManager instance = new WorkgroupManager(); private EmailTranscriptEvent emailTranscriptEvent; /** * Returns a workgroup manager instance (singleton). * * @return a workgroup manager instance. */ public synchronized static WorkgroupManager getInstance() { if (instance == null) { instance = new WorkgroupManager(); } return instance; } /** * Track all Workgroups */ private Map<String, Workgroup> workgroups = new ConcurrentHashMap<String, Workgroup>(); /** * Tracks the last known workgroup open status */ private Map<Long, Workgroup.Status> workgroupOpenStatus = new HashMap<Long, Workgroup.Status>(); private AgentManager agentManager; public List iqHandlers = new LinkedList(); private int defaultMaxChats = 4; // default value, usually overridden by config file private int defaultMinChats = 1; private long defaultOfferTimeout = 20 * 1000; // 20 seconds private long defaultRequestTimeout = 4 * 60 * 1000; // 4 minutes /** * <p>Simple flag to track whether the workgroups have been loaded or not.</p> */ private boolean loaded = false; private ReentrantReadWriteLock workgroupLock = new ReentrantReadWriteLock(); private JID serviceAddress = null; private String mucServiceName; /** * Holds the object responsible for handling disco#info packets to this manager. */ private IQDiscoInfoHandler iqDiscoInfoHandler; /** * Holds the object responsible for handling disco#items packets to this manager. */ private IQDiscoItemsHandler iqDiscoItemsHandler; /** * Holds the object responsible for handling transcript searches for this manager. */ private IQChatSearchHandler iqChatSearchHandler = new IQChatSearchHandler(this); /** * Manager that keeps the list of ad-hoc commands and processing command requests. */ private AdHocCommandManager commandManager; private GroupEventListener groupEventListener; private TimerTask presenceCheckTask; /** * Creates a workgroup manager implementation. */ public WorkgroupManager() { // Load Chat Properties String minChats = JiveGlobals.getProperty("xmpp.live.defaults.minchats"); String maxChats = JiveGlobals.getProperty("xmpp.live.defaults.maxchats"); if (minChats != null && minChats.trim().length() > 0) { defaultMinChats = Integer.parseInt(minChats); } if (maxChats != null && maxChats.trim().length() > 0) { defaultMaxChats = Integer.parseInt(maxChats); } String offerTimeout = JiveGlobals.getProperty("xmpp.live.defaults.offerTimeout"); String requestTimeout = JiveGlobals.getProperty("xmpp.live.defaults.requestTimeout"); if (offerTimeout != null && offerTimeout.trim().length() > 0) { defaultOfferTimeout = Integer.parseInt(offerTimeout); } if (requestTimeout != null && requestTimeout.trim().length() > 0) { defaultRequestTimeout = Integer.parseInt(requestTimeout); } // Initialize chat settings manager. TODO This will be moved over to an extension file. ChatSettingsManager.getInstance(); // Initialize EmailTranscript Event. TODO This will be moved over to an extension file. emailTranscriptEvent = new EmailTranscriptEvent(); addGroupManagerListener(); // Create responsible for handling ad-hoc commands in this service commandManager = new AdHocCommandManager(); iqDiscoInfoHandler = new IQDiscoInfoHandler(this, commandManager); iqDiscoItemsHandler = new IQDiscoItemsHandler(this, commandManager); presenceCheckTask = new TimerTask() { @Override public void run() { handleOutdatePresence(); } }; TaskEngine.getInstance().scheduleAtFixedRate(presenceCheckTask, 5000, 5000); } public void start() { // Enable the shared secret SASL mechanism, which the Fastpath web client will use. // We use a custom SASL mechanism so that web-based customer chats can login without // a username or password. However, a shared secret key is still required so that // anonymous login doesn't have to be enabled for the whole server. if (!JiveSharedSecretSaslServer.isSharedSecretAllowed()) { JiveSharedSecretSaslServer.setSharedSecretAllowed( true ); } // If the database was just created then create the "demo" user and "demo" workgroup // Workgroup creation requires MUC service address so we need to run this code after the // disco stuff if (!JiveGlobals.getBooleanProperty("fastpath.database.setup")) { boolean createUser = createDemoUser(); if (createUser) { createDemoWorkgroup(); } JiveGlobals.setProperty("fastpath.database.setup", "true"); } // Register ad-hoc commands commandManager.addCommand(new CreateWorkgroup()); commandManager.addCommand(new DeleteWorkgroup()); } public void shutdown() { workgroups.clear(); GroupEventDispatcher.removeListener(groupEventListener); instance = null; ChatSearchManager.shutdown(); ChatSettingsManager.shutdown(); RoutingManager.shutdown(); WorkgroupProviderManager.shutdown(); emailTranscriptEvent.shutdown(); workgroupComparator = null; TaskEngine.getInstance().cancelScheduledTask(presenceCheckTask); } public void stop() { for (Workgroup workgroup : getWorkgroups()) { workgroup.shutdown(); } workgroups.clear(); workgroupOpenStatus.clear(); } public AgentManager getAgentManager() { return agentManager; } public int getDefaultMinChats() { return defaultMinChats; } public void setDefaultMinChats(int minChats) { if (minChats >= 0) { defaultMinChats = minChats; JiveGlobals.setProperty("xmpp.live.defaults.minchats", Integer.toString(minChats)); } } public int getDefaultMaxChats() { return defaultMaxChats; } public void setDefaultMaxChats(int maxChats) { if (maxChats >= 0) { defaultMaxChats = maxChats; JiveGlobals.setProperty("xmpp.live.defaults.maxchats", Integer.toString(maxChats)); } } public long getDefaultOfferTimeout() { return defaultOfferTimeout; } public void setDefaultOfferTimeout(long defaultOfferTimeout) { if (defaultOfferTimeout >= 0) { this.defaultOfferTimeout = defaultOfferTimeout; JiveGlobals.setProperty("xmpp.live.defaults.offerTimeout", Long.toString(defaultOfferTimeout)); } } public long getDefaultRequestTimeout() { return defaultRequestTimeout; } public void setDefaultRequestTimeout(long defaultRequestTimeout) { if (defaultRequestTimeout >= 0) { this.defaultRequestTimeout = defaultRequestTimeout; JiveGlobals.setProperty("xmpp.live.defaults.requestTimeout", Long.toString(defaultRequestTimeout)); } } /** * Creates a workgroup with default settings. * * @param name the name of the workgroup. * @return the created workgroup * @throws UnauthorizedException if not allowed to create the workgroup. * @throws UserAlreadyExistsException If the address is already in use */ public Workgroup createWorkgroup(String name) throws UserAlreadyExistsException, UnauthorizedException { if (workgroups.containsKey(name + "@" + serviceAddress.toBareJID())) { throw new UserAlreadyExistsException(name); } // Reserve the username - user ID from the jiveUserID table long id = -1; Workgroup workgroup = null; try { id = SequenceManager.nextID(FastpathConstants.WORKGROUP); boolean workgroupAdded = addWorkgroup(id, name); if (workgroupAdded) { workgroupLock.writeLock().lock(); try { workgroup = new Workgroup(id, agentManager); workgroups.put(workgroup.getJID().toBareJID(), workgroup); workgroupOpenStatus.put(workgroup.getID(), workgroup.getStatus()); } finally { workgroupLock.writeLock().unlock(); } // Create a chat room for this workgroup workgroup.createGroupChatRoom(); // Trigger the event that a workgroup has been created WorkgroupEventDispatcher.workgroupCreated(workgroup); } else { throw new UnauthorizedException("Could not insert workgroup in database"); } } catch (Exception e) { Log.error(e.getMessage(), e); if (id != -1) { try { if (workgroup != null) { workgroups.remove(workgroup.getJID().toBareJID()); workgroupOpenStatus.remove(workgroup.getID()); deleteWorkgroup(id); } } catch (Exception e1) { Log.error(e1.getMessage(), e1); } } if (e instanceof UserAlreadyExistsException) { throw (UserAlreadyExistsException)e; } else { throw new UnauthorizedException(); } } return workgroup; } /** * Remove a workgroup from the system. * * @param workgroup the workgroup to remove. * @throws UnauthorizedException if not allowed to delete the workgroup. */ public void deleteWorkgroup(Workgroup workgroup) throws UnauthorizedException { if (!loaded) { throw new IllegalStateException("Workgroup Manager not loaded yet"); } // Trigger the event that a workgroup is being deleted WorkgroupEventDispatcher.workgroupDeleting(workgroup); deleteWorkgroup(workgroup.getID()); // Notify the workgroup that it is being destroyed workgroup.destroy(); workgroupLock.writeLock().lock(); try { workgroups.remove(workgroup.getJID().toBareJID()); workgroupOpenStatus.remove(workgroup.getID()); } finally { workgroupLock.writeLock().unlock(); } // Delete the queues of the workgroup for (RequestQueue requestQueue : workgroup.getRequestQueues()) { workgroup.deleteRequestQueue(requestQueue); } // Trigger the event that a workgroup has been deleted WorkgroupEventDispatcher.workgroupDeleted(workgroup); } /** * Returns the number of workgroups in the system. * * @return the number of workgroups in the system. */ public int getWorkgroupCount() { return workgroups.size(); } /** * Returns the workgroup mapped to a specific JID. * * @param jid the JID mapped to the workgroup. * @return the workgroup with the specified JID. * @throws UserNotFoundException if the workgroup could not be loaded. */ public Workgroup getWorkgroup(JID jid) throws UserNotFoundException { Workgroup wg = workgroups.get(jid.toBareJID()); if (wg == null) { throw new UserNotFoundException(jid.toBareJID()); } return wg; } /** * Return a workgroup based on it's node. * * @param workgroupName the name of the workgroup. * @return the workgroup or null if no workgroup is found. */ public Workgroup getWorkgroup(String workgroupName) { for (Workgroup workgroup : getWorkgroups()) { if (workgroup.getJID().getNode().equalsIgnoreCase(workgroupName)) { return workgroup; } } return null; } /** * Returns all workgroups within the system sorted by ID. * * @return the collection of workgroups. */ public Collection<Workgroup> getWorkgroups() { if (workgroups.isEmpty()) { return Collections.emptyList(); } final List<Workgroup> copy = new ArrayList<Workgroup>(workgroups.values()); Collections.sort(copy, workgroupComparator); return Collections.unmodifiableCollection(copy); } public Iterator<Workgroup> getWorkgroups(WorkgroupResultFilter filter) { final List<Workgroup> wgroups = new ArrayList<Workgroup>(workgroups.values()); Collections.sort(wgroups, workgroupComparator); Iterator<Workgroup> groups = filter.filter(wgroups.iterator()); if (groups == null) { groups = Collections.EMPTY_LIST.iterator(); } return groups; } /** * Returns the handler for disco#info packets sent to the workgroup service. The returned * handler may be used for configuring its features providers. * * @return the handler for disco#info packets sent to the workgroup service. */ public IQDiscoInfoHandler getIqDiscoInfoHandler() { return iqDiscoInfoHandler; } /** * <p>Trigger an open check every 25 seconds after an initial 45 second delay.</p> * <p/> * <p>The workgroup knows when it is open according to it's own settings including * schedule information. However, the schedule is only checked on demand when new * users requests are submitted, or agents query the status of the workgroup. * So we must force a check of the workgroup open status periodically to see * if it has changed, and if so, to have the workgroup broadcast its presence. * This is potentially more bandwidth intensive than having each workgroup watch * it's own schedule but uses less threads.</p> * TODO: trace down all events that cause a state change so we don't have to poll */ private void startTimer() { TaskEngine taskEngine = TaskEngine.getInstance(); taskEngine.schedule(new TimerTask() { @Override public void run() { workgroupLock.readLock().lock(); try { for (Workgroup group : workgroups.values()) { Workgroup.Status currentOpen = group.getStatus(); Workgroup.Status oldOpen = workgroupOpenStatus.get(group.getID()); if (oldOpen != currentOpen) { group.broadcastQueuesStatus(); workgroupOpenStatus.put(group.getID(), currentOpen); if (Workgroup.Status.OPEN != oldOpen && Workgroup.Status.OPEN == currentOpen) { // Trigger the event that the workgroup has been opened group.notifyOpened(); } else if (Workgroup.Status.OPEN == oldOpen) { // Trigger the event that the workgroup has been closed group.notifyClosed(); } } } } finally { workgroupLock.readLock().unlock(); } } }, 45000, 9000); // Every 5 minutes let the workgroups clean up dead requests or dead rooms. This may occur // if the connections were lost or the invitations were lost or whatever taskEngine.schedule(new TimerTask() { @Override public void run() { workgroupLock.readLock().lock(); try { for (Workgroup group : workgroups.values()) { group.cleanup(); } } finally { workgroupLock.readLock().unlock(); } } }, 60000, 300000); // Every 15 seconds check for not answered room invitations taskEngine.schedule(new TimerTask() { @Override public void run() { workgroupLock.readLock().lock(); try { for (Workgroup group : workgroups.values()) { group.checkRequests(); } } finally { workgroupLock.readLock().unlock(); } } }, 10000, 15000); // Every 30 seconds check if the search index of the workgroups should be updated taskEngine.schedule(new TimerTask() { @Override public void run() { workgroupLock.readLock().lock(); try { for (Workgroup group : workgroups.values()) { try { ChatSearchManager.getInstanceFor(group).updateIndex(false); } catch (IOException e) { Log.error(e.getMessage(), e); } } } finally { workgroupLock.readLock().unlock(); } } }, 10000, 30000); } void updateWorkgroupStatus(Workgroup workgroup) { Workgroup.Status newStatus = workgroup.getStatus(); Workgroup.Status oldStatus = workgroupOpenStatus.put(workgroup.getID(), newStatus); if (Workgroup.Status.OPEN != oldStatus && Workgroup.Status.OPEN == newStatus) { // Trigger the event that the workgroup has been opened workgroup.notifyOpened(); } else if (Workgroup.Status.OPEN == oldStatus && Workgroup.Status.OPEN != newStatus) { // Trigger the event that the workgroup has been closed workgroup.notifyClosed(); } } public String getDefaultChatServer() { return getMUCServiceName(); } private boolean addWorkgroup(long workgroupID, String workgroupName) { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(ADD_WORKGROUP); Date now = new Date(); pstmt.setLong(1, workgroupID); pstmt.setString(2, workgroupName); pstmt.setString(3, workgroupName); pstmt.setString(4, "None"); pstmt.setInt(5, 0); // start workgroups closed pstmt.setString(6, StringUtils.dateToMillis(now)); pstmt.setString(7, StringUtils.dateToMillis(now)); pstmt.setInt(8, -1); pstmt.setInt(9, -1); pstmt.setLong(10, -1); pstmt.setLong(11, -1); pstmt.setInt(12, 0); // start schedule mode to manual pstmt.executeUpdate(); return true; } catch (SQLException ex) { Log.error(ex.getMessage(), ex); } finally { DbConnectionManager.closeConnection(pstmt, con); } return false; } private void deleteWorkgroup(long workgroupID) { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(DELETE_WORKGROUP); pstmt.setLong(1, workgroupID); pstmt.executeUpdate(); } catch (SQLException ex) { Log.error(ex.getMessage(), ex); } finally { DbConnectionManager.closeConnection(pstmt, con); } } private void loadWorkgroups() { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(LOAD_WORKGROUPS); rs = pstmt.executeQuery(); workgroupLock.writeLock().lock(); while (rs.next()) { Workgroup workgroup = new Workgroup(rs.getLong(1), agentManager); workgroups.put(workgroup.getJID().toBareJID(), workgroup); workgroupOpenStatus.put(workgroup.getID(), workgroup.getStatus()); } } catch (SQLException ex) { Log.error(ex.getMessage(), ex); } finally { workgroupLock.writeLock().unlock(); DbConnectionManager.closeConnection(rs, pstmt, con); } // Initialize routing manager RoutingManager.getInstance(); } public void processPacket(Packet packet) { try { // Check if the packet is a disco request if (packet instanceof IQ) { if (process((IQ)packet)) { return; } } // Check if the packet was sent to an existent workgroup. If a workgroup // was found then let the workgroup process the packet try { Workgroup workgroup = getWorkgroup(packet.getTo()); workgroup.process(packet); } catch (UserNotFoundException e) { // Answer a not_authorized error since the workgroup was not found. A // not_acceptable error was chosen since we are returning that same error when // sending presences to a workgroup from a JID that is not an agent or when the // agent does not belong to the workgroup. Answering the same error code ensures // some kind of security since the sender cannot distinguish between any of the // above situations if (packet instanceof Presence) { if (((Presence)packet).getType() == Presence.Type.error) { // Skip Presence packets of type error return; } Presence reply = new Presence(); reply.setID(packet.getID()); reply.setTo(packet.getFrom()); reply.setFrom(packet.getTo()); reply.setError(PacketError.Condition.not_authorized); send(reply); } else if (packet instanceof IQ) { if (((IQ)packet).getType() == IQ.Type.error) { // Skip IQ packets of type error return; } IQ reply = IQ.createResultIQ((IQ)packet); reply.setChildElement(((IQ)packet).getChildElement().createCopy()); reply.setError(PacketError.Condition.not_authorized); send(reply); } else { if (((Message)packet).getType() == Message.Type.error) { // Skip Message packets of type error return; } Message reply = new Message(); reply.setID(packet.getID()); reply.setTo(packet.getFrom()); reply.setFrom(packet.getTo()); reply.setError(PacketError.Condition.not_authorized); send(reply); } } } catch (Exception e) { Log.error(e.getMessage(), e); } } /** * Returns true if the IQ packet was processed. This method should only process disco packets * sent to the workgroup service. * * @param iq the IQ packet to process. * @return true if the IQ packet was processed. */ private boolean process(IQ iq) { if (iq.getType() == IQ.Type.error) { // Skip IQ packets of type error return false; } Element childElement = iq.getChildElement(); String name = null; String namespace = null; if (childElement != null) { namespace = childElement.getNamespaceURI(); name = childElement.getName(); } if ("http://jabber.org/protocol/disco#info".equals(namespace)) { IQ reply = iqDiscoInfoHandler.handleIQ(iq); if (reply != null) { send(reply); } } else if ("http://jabber.org/protocol/disco#items".equals(namespace)) { IQ reply = iqDiscoItemsHandler.handleIQ(iq); if (reply != null) { send(reply); } } else if ("jabber:iq:version".equals(namespace)) { IQ reply = IQ.createResultIQ(iq); Element version = reply.setChildElement("query", "jabber:iq:version"); version.addElement("name").setText("Spark Fastpath"); version.addElement("version").setText("3.2"); version.addElement("os").setText("Java 5"); send(reply); } else if ("workgroups".equals(name)) { try { // Check that the sender of this IQ is an agent getAgentManager().getAgent(iq.getFrom()); // Get the agent JID to return his workgroups String agentJID = childElement.attributeValue("jid"); try { // Answer the workgroups where the agent can work in Agent agent = getAgentManager().getAgent(new JID(agentJID)); sendWorkgroups(iq, agent); } catch (AgentNotFoundException e) { IQ reply = IQ.createResultIQ(iq); reply.setChildElement(iq.getChildElement().createCopy()); reply.setError(new PacketError(PacketError.Condition.item_not_found)); send(reply); } } catch (AgentNotFoundException e) { IQ reply = IQ.createResultIQ(iq); reply.setChildElement(iq.getChildElement().createCopy()); reply.setError(new PacketError(PacketError.Condition.not_authorized)); send(reply); } } else if ("transcript-search".equals(name)) { iqChatSearchHandler.handleIQ(iq); } else if ("http://jabber.org/protocol/commands".equals(namespace)) { // Process ad-hoc command IQ reply = commandManager.process(iq); send(reply); } else { return false; } return true; } private void sendWorkgroups(IQ request, Agent agent) { IQ reply = IQ.createResultIQ(request); Element workgroupsElement = reply.setChildElement("workgroups", "http://jabber.org/protocol/workgroup"); workgroupsElement.addAttribute("jid", agent.getAgentJID().toBareJID()); for (Workgroup workgroup : getWorkgroups()) { if (workgroup.getAgents().contains(agent)) { // Add the information of the workgroup Element workgroupElement = workgroupsElement.addElement("workgroup"); workgroupElement.addAttribute("jid", workgroup.getJID().toBareJID()); } } send(reply); } public String getName() { return "Workgroup Plugin"; } public String getDescription() { return "Workgroup plugin for Live Assistance."; } public void initialize(JID jid, ComponentManager componentManager) throws ComponentException { // Set the full domain address that this component is serving serviceAddress = jid; agentManager = new AgentManager(); // Set a default MUC service JID. This may be required when the server does // not support service discovery mucServiceName = "conference." + componentManager.getServerName(); // Send a disco request to discover the MUC service address IQ disco = new IQ(IQ.Type.get); disco.setTo(componentManager.getServerName()); disco.setFrom(jid); disco.setChildElement("query", "http://jabber.org/protocol/disco#items"); send(disco); // Start the background processes startTimer(); // Load Workgroups loadWorkgroups(); // Set that the workgroups have been loaded loaded = true; } public void send(Packet packet) { try { ComponentManagerFactory.getComponentManager().sendPacket(this, packet); } catch (ComponentException e) { // Do nothing. This error should never happen Log.error(e.getMessage(), e); } } public JID getAddress() { return serviceAddress; } /** * Returns the service name for MUC * * @return the MUC Service Name */ public final String getMUCServiceName() { return mucServiceName; } public void setMUCServiceName(String mucServiceName) { this.mucServiceName = mucServiceName; } /** * Listens for changes in the Group model to update respective agents. */ private void addGroupManagerListener() { groupEventListener = new GroupEventListener() { public void groupCreated(Group group, Map params) { } public void groupDeleting(Group group, Map params) { } public void groupModified(Group group, Map params) { } public void memberAdded(Group group, Map params) { String userJID = (String)params.get("member"); JID jid = new JID(userJID); if (!agentManager.hasAgent(jid)) { for (Workgroup workgroup : workgroups.values()) { for (RequestQueue queue : workgroup.getRequestQueues()) { if (queue.hasGroup(group)) { agentManager.getAgents(group); } } } } } public void memberRemoved(Group group, Map params) { } public void adminAdded(Group group, Map params) { } public void adminRemoved(Group group, Map params) { } }; GroupEventDispatcher.addListener(groupEventListener); } /** * Creates a demo user account. * * @return true if the user account was created. */ private boolean createDemoUser() { // Do nothing if user store is read-only if (UserManager.getUserProvider().isReadOnly()) { return false; } try { UserManager.getInstance().createUser("demo", "demo", "Fastpath Demo Account", "demo@fastpath.com"); return true; } catch (Exception e) { Log.error(e.getMessage(), e); } return false; } /** * Creates a demo workgroup. * * @return true if the workgroup was created. */ private boolean createDemoWorkgroup() { // Create example workgroup try { if (WorkgroupUtils.createWorkgroup("demo", "Demo workgroup", "demo").size() == 0) { JiveGlobals.setProperty("demo.workgroup", "true"); } } catch (Exception e) { Log.error(e.getMessage(), e); return false; } return true; } /** * Checks for outdated presences caused by network failures, etc. */ private void handleOutdatePresence() { for (Workgroup workgroup : getWorkgroups()) { for (AgentSession agentSession : workgroup.getAgentSessions()) { final JID agentJID = agentSession.getJID(); final PresenceManager presenceManager = XMPPServer.getInstance().getPresenceManager(); boolean isOnline = false; for (Presence presence : presenceManager.getPresences(agentJID.getNode())) { if (presence.getFrom().equals(agentJID)) { isOnline = true; } } if (!isOnline) { // Send offline presence to workgroup. for (Workgroup wgroup : agentSession.getWorkgroups()) { Presence presence = new Presence(); presence.setFrom(agentJID); presence.setTo(wgroup.getJID()); presence.setType(Presence.Type.unavailable); wgroup.getWorkgroupPresenceHandler().process(presence); } } } } } /** * Sorts all <code>Workgroups</code> by Display Name. */ static Comparator<Workgroup> workgroupComparator = new Comparator<Workgroup>() { public int compare(Workgroup item1, Workgroup item2) { String str1 = item1.getDisplayName(); String str2 = item2.getDisplayName(); return str1.compareToIgnoreCase(str2); } }; }