/* * 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.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Comparator; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.xmpp.workgroup.spi.JiveLiveProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.component.ComponentManagerFactory; import org.xmpp.packet.IQ; import org.xmpp.packet.JID; /** * Workgroup agents, which are stored in the database. * * @author Derek DeMoro */ public class Agent { private static final Logger Log = LoggerFactory.getLogger(Agent.class); private static final String LOAD_AGENT = "SELECT name, agentJID, maxchats FROM fpAgent WHERE agentID=?"; private static final String SAVE_AGENT = "UPDATE fpAgent SET name=?, agentJID=?, maxchats=? WHERE agentID=?"; /** * The agent session created when the agent joined the service. */ private AgentSession session; /** * The ceiling on the maximumn number of chats the agent should handle. */ protected int maxChats = 0; /** * Nickname of the agent. */ private String nickname; /** * Custom properties for the agent. */ private JiveLiveProperties properties; /** * The id of the agent. */ private long id; /** * The XMPP address of the Agent. */ private JID agentJID; public Agent(long agentID) { this.id = agentID; // Load this individual agent loadAgent(agentID); } @Override public String toString() { return "AI-" + Integer.toHexString(hashCode()) + " JID " + agentJID.toString() + " MAX " + Integer.toString(maxChats); } /** * Returns the session of the agent or <tt>null</tt> if the agent has not logged.<p> * * @return the session of the agent in the workgroup service or null if user is not logged. */ public AgentSession getAgentSession() { return session; } /** * Creates a new agent session for the agent connected at the given full JID. The user can * only be logged from one resource. If a user tries to have more than one session (i.e. tries * to join from two different resources) then depending on the settings one of this outcomes * could happen: 1) the existing session is closed and a new session is created or 2) the new * session fails to be created and a <tt>null</tt> value is returned. * * @param userJID the full JID of the user that is creating a new session. * @return the new agent session of <tt>null</tt> if the session failed to be created. */ public synchronized AgentSession createSession(JID userJID) { if (session != null) { // Verify that existing session belongs to the same full JID if (!session.getJID().equals(userJID)) { // Check that existing session is joined to at least one group if (!session.getWorkgroups().isEmpty()) { // TODO Implement conflict policy to decide if existing session is closed and new one is created or new one fails and null is returned // Handle conflict since the same agent is trying to connect from 2 different resources return null; } } else { // User is already connected from this resource so return the existing session return session; } } session = new AgentSession(userJID, this); return session; } public synchronized void closeSession(JID userJID) { if (session != null) { // Verify that existing session belongs to the same full JID if (session.getJID().equals(userJID)) { session = null; } } } public String getNickname() { // Lazy initialize the nickname based on the JID's node. This is useful // for compatibility with old versions where nickname may be null. if (nickname == null && agentJID != null) { nickname = agentJID.getNode(); } return nickname; } public void setNickname(String name) { // Do nothing if setting the same old value if (name != null && name.equals(nickname)) { return; } // Set the new value this.nickname = name; // Save the new Agent's state to the database saveAgent(); } public Element getAgentInfo() { // Create an agent element Element element = DocumentHelper.createElement(QName.get("agent", "http://jabber.org/protocol/workgroup")); element.addAttribute("jid", getAgentJID().toString()); // Add the name of the agent if (getNickname() != null) { element.addElement("name", "http://jivesoftware.com/protocol/workgroup").setText(getNickname()); } return element; } public DbProperties getProperties() { if (properties == null) { properties = new JiveLiveProperties("fpAgentProp", id); } return properties; } private void loadAgent(long agentID) { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(LOAD_AGENT); pstmt.setLong(1, agentID); rs = pstmt.executeQuery(); if (rs.next()) { nickname = rs.getString(1); String agentJID = rs.getString(2); // If the agentJID was just a username then create a JID where the domain is the // local server if (!agentJID.contains("@")) { agentJID = agentJID + "@" + ComponentManagerFactory.getComponentManager().getServerName(); } this.agentJID = new JID(agentJID); maxChats = rs.getInt(3); } } catch (Exception ex) { Log.error(ex.getMessage(), ex); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } } private void saveAgent() { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(SAVE_AGENT); pstmt.setString(1, nickname); // 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.setInt(3, maxChats); pstmt.setLong(4, id); pstmt.executeUpdate(); } catch (Exception ex) { Log.error(ex.getMessage(), ex); } finally { DbConnectionManager.closeConnection(pstmt, con); } } public Long getID(){ return id; } public void setAgentJID(JID agentJID) { // Do nothing if setting the same old value if (agentJID != null && agentJID.equals(agentJID)) { return; } // Set the new value this.agentJID = agentJID; // Save the new Agent's state to the database saveAgent(); } public JID getAgentJID(){ return agentJID; } /** * This agent has been added to a queue so we need to inform the existing agents of the queue * ,that previously requested agent information, of this new agent. * * @param requestQueue the queue where this agent has been added. */ public void sendAgentAddedToAllAgents(RequestQueue requestQueue) { Workgroup workgroup = requestQueue.getWorkgroup(); for (AgentSession session : workgroup.getAgentSessions()) { if (session.hasRequestedAgentInfo()) { IQ iq = new IQ(IQ.Type.set); iq.setFrom(workgroup.getJID()); iq.setTo(session.getJID()); Element agentStatusRequest = iq.setChildElement("agent-status-request", "http://jabber.org/protocol/workgroup"); agentStatusRequest.add(getAgentInfo()); // Push the new agent info to the agent workgroup.send(iq); } } } /** * This agent has been removed from a queue so we need to inform the existing agents of the * queue ,that previously requested agent information, of the agent deletion. * * @param requestQueue the queue from where this agent has been deleted. */ public void sendAgentRemovedToAllAgents(RequestQueue requestQueue) { Workgroup workgroup = requestQueue.getWorkgroup(); for (AgentSession session : workgroup.getAgentSessions()) { if (session.hasRequestedAgentInfo()) { IQ iq = new IQ(IQ.Type.set); iq.setFrom(workgroup.getJID()); iq.setTo(session.getJID()); Element agentStatusRequest = iq.setChildElement("agent-status-request", "http://jabber.org/protocol/workgroup"); Element agentInfo = getAgentInfo(); agentInfo.addAttribute("type", "remove"); agentStatusRequest.add(agentInfo); // Push the new agent info to the agent workgroup.send(iq); } } } public void updateAgentInfo(IQ packet) { Element agentInfo = packet.getChildElement(); // Set the new agent's name Element element = agentInfo.element("name"); if (element != null) { setNickname(element.getTextTrim()); } // Commented since we don't want the agent to change its JID address /*element = agentInfo.element("jid"); if (element != null) { setAgentJID(new JID(element.getTextTrim())); }*/ } /** * <p>A comparator that sorts agents by address (toBareStringPrep()).</p> * <p>The comparator does not handle other objects, using Agents with any other * object type in the same sorted container will cause a ClassCastException to be thrown.</p> */ class AgentAddressComparator implements Comparator<Agent> { public int compare(Agent o1, Agent o2) { return o1.getAgentJID().toBareJID().compareTo(o2.getAgentJID().toBareJID()); } } }