/* * 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.routing; 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.List; import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.util.NotFoundException; import org.jivesoftware.xmpp.workgroup.RequestQueue; import org.jivesoftware.xmpp.workgroup.Workgroup; import org.jivesoftware.xmpp.workgroup.request.UserRequest; import org.jivesoftware.xmpp.workgroup.spi.routers.WordMatchRouter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides a registration and event processing for all <code>RequestRouter</code>s. * * @author Derek DeMoro */ public final class RoutingManager { private static final Logger Log = LoggerFactory.getLogger(RoutingManager.class); private static final String ADD_ROUTING_RULE = "INSERT INTO fpRouteRule (workgroupID, queueID, rulePosition, query) VALUES (?,?,?,?)"; private static final String DELETE_ROUTING_RULE = "DELETE FROM fpRouteRule WHERE workgroupID=? AND rulePosition=?"; private static final String LOAD_RULES = "SELECT queueID, rulePosition, query FROM fpRouteRule WHERE workgroupID=?"; private static final String UPDATE_RULE_POSITION = "UPDATE fpRouteRule SET rulePosition=? WHERE workgroupID=? AND rulePosition=?"; private static RoutingManager singleton = new RoutingManager(); /** * Returns the singleton instance of <CODE>RoutingManager</CODE>, * creating it if necessary. * <p/> * * @return the singleton instance of <Code>RoutingManager</CODE> */ public static RoutingManager getInstance() { return singleton; } public static void shutdown() { singleton = null; } private RoutingManager() { } /** * Returns the best {@link RequestQueue} in the specified {@link Workgroup} that could * handle the specified {@link UserRequest}. * * @param workgroup the workgroup where a queue will be searched. * @param request the user request to be handled. * @return the best RequestQueue in the specified Workgroup. */ public RequestQueue getBestQueue(Workgroup workgroup, UserRequest request) { WordMatchRouter router = new WordMatchRouter(); for (RoutingRule rule : getRoutingRules(workgroup)) { String query = rule.getQuery(); boolean handled = router.checkForHits(request.getMetaData(), query); if (handled) { // Retrieve queue and route to it. try { return workgroup.getRequestQueue(rule.getQueueID()); } catch (NotFoundException e) { Log.error(e.getMessage(), e); } } } List<RequestQueue> availableRequestQueues = new ArrayList<RequestQueue>(); // Route to best queue based on availability. for (RequestQueue requestQueue : workgroup.getRequestQueues()) { // Skip queues that do not have agents at the moment if (requestQueue != null && requestQueue.isOpened()) { availableRequestQueues.add(requestQueue); } } Collections.sort(availableRequestQueues, queueComparator); return availableRequestQueues.get(0); } /** * Routes a request to all registers globalRouters within the Live Assistant Server. * * @param workgroup the workgroup the request came in on. * @param request the actual request. */ public void routeRequest(Workgroup workgroup, UserRequest request) { getBestQueue(workgroup, request).addRequest(request); } /** * Adds a new Routing Rule to the database. * * @param workgroup the <code>Workgroup</code> the routing rule belongs to. * @param queueID the id of the queue. * @param position the position of the routing rule. * @param query the lucense query */ public void addRoutingRule(Workgroup workgroup, long queueID, int position, String query) { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(ADD_ROUTING_RULE); pstmt.setLong(1, workgroup.getID()); pstmt.setLong(2, queueID); pstmt.setInt(3, position); pstmt.setString(4, query); pstmt.executeUpdate(); } catch (SQLException e) { Log.error(e.getMessage(), e); } finally { DbConnectionManager.closeConnection(pstmt, con); } } /** * Updates a Routing rule to a new position. * * @param workgroup the workgroup the rule belongs to. * @param position the current position. * @param newPosition the new position. */ public void updateRoutingRule(Workgroup workgroup, int position, int newPosition) { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(UPDATE_RULE_POSITION); pstmt.setInt(1, newPosition); pstmt.setLong(2, workgroup.getID()); pstmt.setInt(3, position); pstmt.execute(); } catch (SQLException e) { Log.error(e.getMessage(), e); } finally { DbConnectionManager.closeConnection(pstmt, con); } } /** * Removes a RoutingRule from the database. * * @param workgroup the workgroup the routing rule belongs to. * @param position the position of the routing rule. */ public void removeRoutingRule(Workgroup workgroup, int position) { Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(DELETE_ROUTING_RULE); pstmt.setLong(1, workgroup.getID()); pstmt.setInt(2, position); pstmt.executeUpdate(); } catch (SQLException e) { Log.error(e.getMessage(), e); } finally { DbConnectionManager.closeConnection(pstmt, con); } } /** * Returns all RoutingRules belong to the given workgroup. * * @param workgroup the workgroup. * @return a Collection of all RoutingRules sorted by position. */ public Collection<RoutingRule> getRoutingRules(Workgroup workgroup) { final List<RoutingRule> rules = new ArrayList<RoutingRule>(); Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(LOAD_RULES); pstmt.setLong(1, workgroup.getID()); rs = pstmt.executeQuery(); while (rs.next()) { long queueID = rs.getLong("queueID"); int position = rs.getInt("rulePosition"); String query = rs.getString("query"); RoutingRule rule = new RoutingRule(queueID, position, query); rules.add(rule); } } catch (Exception ex) { Log.error(ex.getMessage(), ex); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } // Sort by position. Collections.sort(rules, positionComparator); return rules; } private final Comparator<RoutingRule> positionComparator = new Comparator<RoutingRule>() { public int compare(RoutingRule rule1, RoutingRule rule2) { int int1 = rule1.getPosition(); int int2 = rule2.getPosition(); if (int1 == int2) { return 0; } if (int1 > int2) { return 1; } if (int1 < int2) { return -1; } return 0; } }; private final Comparator<RequestQueue> queueComparator = new Comparator<RequestQueue>() { public int compare(RequestQueue queue1, RequestQueue queue2) { int int1 = queue1.getTotalRequestCount(); int int2 = queue2.getTotalRequestCount(); if (int1 == int2) { return 0; } if (int1 > int2) { return 1; } if (int1 < int2) { return -1; } return 0; } }; }