/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* @(#)ConnectionManager.java 1.60 06/29/07
*/
package com.sun.messaging.jmq.jmsserver.service;
import java.util.*;
import java.io.*;
import com.sun.messaging.jmq.io.*;
import com.sun.messaging.jmq.util.lists.WeakValueHashMap;
import com.sun.messaging.jmq.util.GoodbyeReason;
import com.sun.messaging.jmq.jmsserver.util.*;
import com.sun.messaging.jmq.jmsserver.core.DestinationUID;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.management.agent.Agent;
import com.sun.messaging.jmq.jmsserver.resources.BrokerResources;
import com.sun.messaging.jmq.jmsserver.data.handlers.InfoRequestHandler;
import com.sun.messaging.jmq.util.log.Logger;
import com.sun.messaging.jmq.io.Status;
import com.sun.messaging.jmq.util.timer.MQTimer;
/**
* This class maintains the list of all connections
* currently active in the system.
* It allows ConnectionUID's to be mapped to Connections
* and may be used to check connection licensing.
*/
public class ConnectionManager extends WeakValueHashMap
{
private static boolean DEBUG = false;
private Logger logger = Globals.getLogger();
long lastConCheck = 0;
private int limit = 0;
public static int pingTimeout = Globals.getConfig().getIntProperty(
Globals.IMQ + ".ping.interval", 120) * 1000;
public boolean PING_ENABLED = Globals.getConfig().getBooleanProperty(
Globals.IMQ + ".ping.enabled", true);
private ConnectionWatcher connectionWatcher = null;
// lock and counters to allow:
// routing to access connection manager at same time
// prevent adding/removing on connections @ same time
// (although multiple add or destroy operations can happen at once)
// The adding/removing of multiple connections should not happen
// because there is "state" information on one connection (e.g. the
// acked messages) which should be handled before the messages
// are forwarded to a new connection
private int destroyCount =0;
private int addCount =0;
public Object addLock = new Object();
private ConsumerInfoNotifyManager cinmgr = null;
public ConnectionManager(int limit) {
super("ConnectionManager");
this.limit = limit;
cinmgr = new ConsumerInfoNotifyManager(this);
}
public Connection matchProperty(String name, Object value)
{
List al = getConnectionList(null);
Iterator itr = al.iterator();
while (itr.hasNext()) {
Connection c = (Connection)itr.next();
Object v = c.getClientData(name);
if (value == v ||
(value != null && v != null && value.equals(v)))
{
return c;
}
}
return null;
}
public void removeFromClientDataList(String name, Object value)
{
List al = getConnectionList(null);
Iterator itr = al.iterator();
while (itr.hasNext()) {
Connection c = (Connection)itr.next();
Object v = c.getClientData(name);
if (v != null && value != null && (v instanceof List)) {
((List)v).remove(value);
}
}
}
private void startTimer() {
if (PING_ENABLED && connectionWatcher == null) {
lastConCheck = System.currentTimeMillis();
MQTimer timer = Globals.getTimer(true);
connectionWatcher = new ConnectionWatcher();
try {
timer.schedule(connectionWatcher, pingTimeout, pingTimeout);
} catch (IllegalStateException ex) {
logger.log(Logger.DEBUG,"Timer shutting down", ex);
}
}
}
private void stopTimer() {
if (connectionWatcher != null) {
connectionWatcher.cancel();
connectionWatcher = null;
}
}
public void updateConnectionUID(ConnectionUID currentID,
ConnectionUID oldid)
{
synchronized(addLock) {
if (destroyCount > 0) {
try {
addLock.wait();
} catch (InterruptedException ex){
}
}
addCount ++;
}
try {
synchronized(this) {
Object o = this.remove(oldid);
if (o != null)
this.put(currentID, o);
}
} finally {
synchronized(addLock) {
addCount --;
addLock.notifyAll();
}
}
}
/**
* add a new connection
*
* @param con connection to add to the list
* @throws BrokerException if the connection can not
* be added (e.g. licensing has expired)
*/
public void addConnection(Connection con)
throws BrokerException
{
synchronized(addLock) {
if (destroyCount > 0) {
try {
addLock.wait();
} catch (InterruptedException ex){
}
}
addCount ++;
}
try {
if (this.size() > limit) { // throw exception
throw new BrokerException(
Globals.getBrokerResources().getKString(
BrokerResources.X_CONNECTION_LIMIT_EXCEEDED,
con.toString(),
String.valueOf(limit)),
BrokerResources.X_CONNECTION_LIMIT_EXCEEDED,
(Throwable)null, Status.ERROR);
}
synchronized(this) {
this.put(con.getConnectionUID(), con);
if (size() == 1 && PING_ENABLED) { // first connection
startTimer();
}
}
} finally {
synchronized(addLock) {
addCount --;
addLock.notifyAll();
}
}
if (DEBUG) {
logger.log(Logger.DEBUG, BrokerResources.I_NEW_CONNECTION,
con.toString(), String.valueOf(size()));
logCM(Logger.DEBUGHIGH);
}
}
/**
* retrive a connection object from a connection ID
*
* @param con connection to remove
*/
public synchronized Connection getConnection(ConnectionUID id) {
return (Connection)this.get(id);
}
/**
* code to handle removing ALL reverences to a connection
* when it goes away or is closed
*/
public void removeConnection(ConnectionUID id, boolean say_goodbye,
int reason, String reasonStr)
{
if (!containsKey(id)) return; // already gone
/*
* Unregister/Destroy connection MBeans in the JMX agent in the broker.
*/
Agent agent = Globals.getAgent();
if (agent != null) {
agent.notifyConnectionClose(id.longValue());
agent.unregisterConnection(id.longValue());
}
synchronized(addLock) {
if (addCount > 0) {
try {
addLock.wait();
} catch (InterruptedException ex){
}
}
destroyCount ++;
}
try {
// first destroy the connection
Connection con = null;
synchronized(this) {
con= (Connection)this.remove(id);
if (size() == 0 && PING_ENABLED) { // first connection
stopTimer();
}
}
if (con != null) {
destroyConnectionData(con, say_goodbye, reason, reasonStr);
}
} finally {
synchronized(addLock) {
destroyCount --;
addLock.notifyAll();
}
}
}
/**
* method to clean up all stuff associated with a
* connection
*/
private void destroyConnectionData(Connection con, boolean say_goodbye,
int reason, String reasonStr)
{
if (con == null) return;
if (DEBUG) {
logger.log(Logger.DEBUG, BrokerResources.I_REMOVE_CONNECTION,
con.toString(),String.valueOf(size()));
}
if (reason == GoodbyeReason.SHUTDOWN_BKR ) {
con.shutdownConnection(reasonStr);
} else {
con.destroyConnection(say_goodbye, reason, reasonStr);
}
if (DEBUG)
logCM(Logger.DEBUGHIGH);
}
/**
* list out all current connections
*/
private void logCM(int loglevel)
{
logger.log(loglevel, "ConnectionManager: " + size());
Set keys = entrySet();
Iterator itr = keys.iterator();
int i = 0;
while (itr.hasNext()) {
logger.log(loglevel, "{0}:{1}", String.valueOf(i), itr.next().toString());
i ++;
}
}
/**
* remove all connections associated with a Service
*
* should only be called when the svc stoped to accept new connections
*/
/* FOO
public void shutdownAllConnections(Service svc, String reason)
{
if (DEBUG)
logger.log(Logger.DEBUGHIGH, "Removing all connections for {0} ", svc.toString());
List cons = getConnectionList(svc);
Connection con;
for (int i = cons.size()-1; i >= 0; i--) {
con = (Connection)cons.get(i);
destroyConnection(con.getConnectionUID(), true, GoodbyeReason.SHUTDOWN_BKR, reason);
}
} */
public Vector getDebugState(Service svc) {
List cons = getConnectionList(svc);
Vector v = new Vector();
Connection con;
for (int i = cons.size()-1; i >= 0; i--) {
con = (Connection)cons.get(i);
v.add(String.valueOf(con.getConnectionUID().longValue()));
}
return v;
}
/**
* Return number of connections for a particular service.
*
* @param svc Service to count connections for. Null to return
* total number of connections.
*
* @return Number of open connections for the Service (or
* total number of open connections if null svc).
*/
public synchronized int getNumConnections(Service svc)
{
int total = 0;
Iterator itr = values().iterator();
while (itr.hasNext()) {
Connection con = (Connection)itr.next();
if (svc == null || con.getService() == svc) {
total++;
}
}
return total;
}
/**
* Returns the list of connections for a particular service.
*
* @param svc Service to count connections for. Null to return
* total number of connections.
*
* @return List of open connections for the Service (or
* total List of open connections if null svc).
*/
public synchronized List getConnectionList(Service svc)
{
List list = new ArrayList();
Iterator itr = values().iterator();
while (itr.hasNext()) {
Connection con = (Connection)itr.next();
if (svc == null || con.getService() == svc) {
list.add(con);
}
}
return list;
}
public void debug() {
List l = getConnectionList(null);
logger.log(Logger.INFO,"Connection count " + l.size());
Iterator itr = l.iterator();
while (itr.hasNext()) {
Connection c = (Connection)itr.next();
logger.log(Logger.INFO,"Connection " + c);
c.debug("\t");
}
}
/**
* broadcast a message to all connections with flush no-wait
*
* should only be called when all services stoped accept new connections
* @param reason a reason from GoodbyeReason
* @param msg why the goodbye is being broadcast (debugging)
*/
public void broadcastGoodbye(int reason, String msg) {
List cons = getConnectionList(null);
Connection con;
for (int i = cons.size()-1; i >= 0; i--) {
con = (Connection)cons.get(i);
con.sayGoodbye(reason, msg);
}
}
/**
* flush all control messages
*
* should only be called when all services stoped accept new connections
*/
public void flushControlMessages(long time) {
List cons = getConnectionList(null);
Connection con;
for (int i = cons.size()-1; i >= 0; i--) {
con = (Connection)cons.get(i);
con.flushConnection(time);
}
}
/**
* flush all control messages
*
* should only be called when all services stoped accept new connections
*/
public void checkAllConnections() {
List cons = getConnectionList(null);
Connection con;
for (int i = cons.size()-1; i >= 0; i--) {
con = (Connection)cons.get(i);
long access = con.getAccessTime();
if (lastConCheck != 0 && access != 0 &&
access < lastConCheck) {
con.checkConnection();
}
}
lastConCheck = System.currentTimeMillis();
}
public ConsumerInfoNotifyManager getConsumerInfoNotifyManager() {
return cinmgr;
}
protected void sendConsumerInfo(int requestType, DestinationUID duid,
int destType, int infoType, boolean sendToWildcard) {
List cons = getConnectionList(null);
Connection con;
for (int i = cons.size()-1; i >= 0; i--) {
con = (Connection)cons.get(i);
con.sendConsumerInfo(requestType, duid, destType, infoType, sendToWildcard);
}
}
class ConnectionWatcher extends TimerTask {
public void run() {
checkAllConnections();
}
}
public void cleanupMemory(boolean persistent) {
// make sure we quit adding connections unti the
// connections have been cleaned up
if (persistent)
logger.log(Logger.DEBUG,"Swapping all unacknowldged messages from memory (messages will remain persisted )");
else
logger.log(Logger.DEBUG,"Swapping all unacknowldged messages from memory (messages will be swapped to disk)");
}
}