/* * Copyright (C) 2004-2006 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.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.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.SequenceManager; import org.jivesoftware.openfire.group.Group; 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.packet.JID; /** * <p>Simple agent manager implementation without caching or intelligent/lazy loading of data.</p> * <p>This implementation loads all agents into memory so the number of agents is limited by the amount of * available heap. Our biggest customer estimates maybe 50 agents so this doesn't seem to be an unreasonable * implemenation strategy. Should be revisited when looking to scale beyond a few hundred agents.</p> * * @author Derek DeMoro */ public class AgentManager { private static final Logger Log = LoggerFactory.getLogger(AgentManager.class); private static final String LOAD_AGENTS = "SELECT agentID FROM fpAgent"; private static final String INSERT_AGENT = "INSERT INTO fpAgent (agentID, agentJID, name, maxchats, minchats) VALUES (?,?,?,?,?)"; private static final String DELETE_AGENT = "DELETE FROM fpAgent WHERE agentID=?"; private static final String DELETE_AGENT_PROPS = "DELETE FROM fpAgentProp WHERE ownerID=?"; private Map<String, Agent> agents = new HashMap<String, Agent>(); public AgentManager() { loadAgents(); } /** * Returns an agent based on it's <tt>JID</tt>. * * @param agentJID the <tt>JID</tt> of the agent. * @return an Agent * @throws AgentNotFoundException if the agent could not be loaded. */ public Agent getAgent(JID agentJID) throws AgentNotFoundException { Agent agent = agents.get(agentJID.toBareJID()); if (agent == null) { throw new AgentNotFoundException(agentJID.toBareJID()); } return agent; } /** * Returns true if the specified agent exists. * * @param agentJID the <tt>JID</t> of the agent. * @return true if the agent exists, otherwise false. */ public boolean hasAgent(JID agentJID) { try { getAgent(agentJID); } catch (AgentNotFoundException e) { return false; } return true; } /** * Returns an agent based on it's agent ID. * * @param agentID the agentID of the agent. * @return an agent. * @throws AgentNotFoundException if the agent could not be loaded. */ public Agent getAgent(long agentID) throws AgentNotFoundException { for (Agent agent : agents.values()) { if (agent.getID() == agentID) { return agent; } } throw new AgentNotFoundException(Long.toHexString(agentID)); } /** * Returns the number of agents * * @return the total number of agents. */ public int getAgentCount() { return agents.size(); } public Collection<Agent> getAgents() { return Collections.unmodifiableList(new ArrayList<Agent>(agents.values())); } public Iterator<Agent> getAgents(WorkgroupResultFilter filter) { return filter.filter(agents.values().iterator()); } /** * Returns the session of the requested agent. If a session is not found * then a <tt>null</tt> value will be return. If no agent was found for the * specified JID then an AgentNotFoundException will be thrown. * * @param agentJID the JID of the agent to look for an AgentSession. * @return the session of the requested agent in this workgroup. * @throws AgentNotFoundException If no agent was found for the specified JID. */ public AgentSession getAgentSession(JID agentJID) throws AgentNotFoundException { return getAgent(agentJID).getAgentSession(); } /** * Creates a new agent from an <code>XMPPAddress</code> * * @param agentJID the <code>XMPPAddress</code> * @return the <code>Agent</code> created. * @throws IllegalArgumentException if not a valid agent JID, such as a JID without a node. */ public Agent createAgent(JID agentJID) throws IllegalArgumentException { if (!ModelUtil.hasLength((agentJID.getNode()))) { throw new IllegalArgumentException("No anonymous agents allowed"); } // Get the next ID for workgroup agents. long agentID = SequenceManager.nextID(FastpathConstants.WORKGROUP_AGENT); if (!insertAgent(agentID, agentJID)) { throw new IllegalArgumentException("Agent could not be created"); } Agent agent = new Agent(agentID); agent.setAgentJID(agentJID); agent.setNickname(agentJID.getNode()); agents.put(agentJID.toBareJID(), agent); return agent; } /** * Remove the specified agent. * * @param agentJID the jid of the agent to remove. * @throws IllegalArgumentException if not a valid agent JID. */ public void deleteAgent(JID agentJID) throws IllegalArgumentException { Agent agent = agents.remove(agentJID.toBareJID()); if (agent != null) { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(DELETE_AGENT); pstmt.setLong(1, agent.getID()); pstmt.executeUpdate(); pstmt.close(); pstmt = con.prepareStatement(DELETE_AGENT_PROPS); pstmt.setLong(1, agent.getID()); pstmt.executeUpdate(); } catch (SQLException sqle) { Log.error(sqle.getMessage(), sqle); } finally { DbConnectionManager.closeConnection(pstmt, con); } } } /** * Returns all agents belonging to a Shared Group. * * @param group the shared group. * @return the collection of agents. */ public Collection<Agent> getAgents(Group group) { final Set<Agent> set = new HashSet<Agent>(); for (JID jid : group.getMembers()) { Agent agent; try { agent = getAgent(jid); } catch (AgentNotFoundException e) { agent = createAgent(jid); } set.add(agent); } return set; } /** * Return true if the agent belongs to the given workgroup. * * @param agent the agent. * @param workgroup the workgroup. * @return true if the agent is in the workgrouop. */ public boolean isInWorkgroup(Agent agent, Workgroup workgroup) { boolean isMember = false; for (RequestQueue requestQueue : workgroup.getRequestQueues()) { if (!isMember) { isMember = requestQueue.isMember(agent); } } return isMember; } /** * Removes an agent from the system if they no longer belong to any Workgroups. * * @param agent the agent to remove. */ public void removeAgentIfNecessary(Agent agent) { // Go through all workgroups to see if this user is in RequestQueues WorkgroupManager workgroupManager = WorkgroupManager.getInstance(); for (Workgroup workgroup : workgroupManager.getWorkgroups()) { for (RequestQueue queue : workgroup.getRequestQueues()) { if (queue.getMembers().contains(agent)) { return; } for (Group group : queue.getGroups()) { if (group.isUser(agent.getAgentJID())) { return; } } } } try { // Send Presence to Workgroup that this user is no longer available. AgentSession session = agent.getAgentSession(); if (session != null) { for (Workgroup workgroup : session.getWorkgroups()) { session.depart(workgroup); } } // If we get here, then remove from agent. deleteAgent(agent.getAgentJID()); } catch (IllegalArgumentException e) { Log.error(e.getMessage(), e); } } /** * Adds an agent to the Database. * * @param agentID the id of the agent to add. * @param agentJID the jid of the agent. * @return true if the agent was added, otherwise false. */ private boolean insertAgent(long agentID, JID agentJID) { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(INSERT_AGENT); pstmt.setLong(1, agentID); // If the JID of the agent is of the local server then just store the username String hostname = ComponentManagerFactory.getComponentManager().getServerName(); String agentBareJID = agentJID.toBareJID(); if (hostname.equals(agentJID.getDomain())) { agentBareJID = agentJID.getNode(); } pstmt.setString(2, agentBareJID); pstmt.setString(3, agentJID.getNode()); pstmt.setInt(4, -1); pstmt.setInt(5, -1); pstmt.executeUpdate(); return true; } catch (Exception ex) { Log.error(ex.getMessage(), ex); } finally { DbConnectionManager.closeConnection(pstmt, con); } return false; } private void loadAgents() { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(LOAD_AGENTS); rs = pstmt.executeQuery(); while (rs.next()) { final Agent agent = new Agent(rs.getLong(1)); agents.put(agent.getAgentJID().toBareJID(), agent); } } catch (Exception ex) { Log.error(ex.getMessage(), ex); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } } }