/*
* 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.
*/
/*
* @(#)Connection.java 1.66 06/29/07
*/
package com.sun.messaging.jmq.jmsserver.service;
import java.util.*;
import com.sun.messaging.jmq.jmsserver.service.*;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.core.DestinationUID;
import com.sun.messaging.jmq.util.log.Logger;
import com.sun.messaging.jmq.util.ServiceType;
import com.sun.messaging.jmq.jmsserver.resources.BrokerResources;
import java.security.Principal;
import com.sun.messaging.jmq.jmsserver.auth.AccessController;
import com.sun.messaging.jmq.jmsserver.auth.JMQAccessControlContext;
import com.sun.messaging.jmq.auth.api.server.AccessControlContext;
import com.sun.messaging.jmq.io.*;
import com.sun.messaging.jmq.net.IPAddress;
import com.sun.messaging.jmq.jmsserver.util.BrokerException;
import com.sun.messaging.jmq.jmsserver.net.*;
/**
* This interface class is responsible for the basic methods needed to
* send messages back to a client
*
* Each service will implement their own version of this
* Connection interface..
*/
public abstract class Connection
{
// XXX - lks - move to init method ?
private static int clockSkewTime = Globals.getConfig().getIntProperty(
Globals.IMQ + ".clock.skew.interval", 300);
private static int clockSkewCheck = Globals.getConfig().getIntProperty(
Globals.IMQ + ".clock.skew.checkCnt", 0);
private int skewCheckCounter = 0;
private long lastAccess = 0;
private long lastResponse = 0;
protected final Logger logger = Globals.getLogger();
// VERSION STRINGS for PROTOCOL
public static final int UNKNOWN_PROTOCOL = -1;
public static final int SWIFT_PROTOCOL = 200;
public static final int HUMMINGBIRD_PROTOCOL = 201;
public static final int FALCON_PROTOCOL = 300;
public static final int RAPTOR_PROTOCOL=PacketType.VERSION350;
public static final int SHRIKE_PROTOCOL=PacketType.VERSION360;
public static final int SHRIKE4_PROTOCOL=PacketType.VERSION364;
public static final int HAWK_PROTOCOL=PacketType.VERSION400;
public static final int V45_PROTOCOL=PacketType.VERSION450;
// CONNECITON STATE STRINGS
public static final int STATE_UNAVAILABLE = -1;
public static final int STATE_CONNECTED = 0;
public static final int STATE_INITIALIZED = 1;
public static final int STATE_AUTH_REQUESTED = 2;
public static final int STATE_AUTH_RESPONSED = 3;
public static final int STATE_AUTHENTICATED = 4;
public static final int STATE_CLEANED = 5;
public static final int STATE_CLOSED = 6;
public static final int STATE_DESTROYING = 7;
public static final int STATE_DESTROYED = 8;
/**
* State of the connection
*/
protected int state = Connection.STATE_UNAVAILABLE;
/**
* Service object
*/
protected Service service = null;
/**
* Authentication/Authorization controller object
*/
protected AccessController accessController = null;
/**
* Connection ID associated with this Connection
*/
protected ConnectionUID conId = null;
/**
* for reconnectable connections, time to next reconnect
*/
protected long reconnectInterval = 0;
/**
* What version of the protocol the client is speaking. This is
* set when we process the HELLO message
*/
protected int clientProtocolVersion = -1;
/**
* Additional data tagged onto the connection
*/
protected Hashtable clientData = null;
protected boolean isadmin = false;
protected List consumerInfoRequests = Collections.synchronizedList(new ArrayList());
protected List connCloseListeners = new ArrayList();
public Connection(Service svc) throws BrokerException
{
setService(svc);
isadmin = svc.getServiceType() == ServiceType.ADMIN;
}
/**
* The debug state of this object
*/
public Hashtable getDebugState() {
Hashtable ht = new Hashtable();
ht.put("uid", String.valueOf(conId.longValue()));
ht.put("service", service.toString());
ht.put("state", getConnectionStateString(state));
if (clientData != null)
ht.put("clientData", clientData.toString());
else
ht.put("clientData", "none");
ht.put("clientProtocol", String.valueOf(clientProtocolVersion));
ht.put("reconnectInterval", String.valueOf(reconnectInterval));
ht.put("lastAccess", String.valueOf(lastAccess));
ht.put("lastResponse", String.valueOf(lastResponse));
return ht;
}
public void setReconnectInterval(long val)
{
reconnectInterval = val;
}
public long getReconnectInterval() {
return reconnectInterval;
}
public boolean isAdminConnection() {
return isadmin;
}
public static int getHighestSupportedProtocol()
{
return V45_PROTOCOL;
}
public abstract void startConnection();
public abstract void stopConnection();
public abstract void cleanupConnection();
public abstract void closeConnection(boolean force, int reason, String reasonStr);
public abstract void shutdownConnection(String reasonStr);
public void destroyConnection(boolean force, int reason, String reasonStr)
{
service.removeConnection(conId, reason, reasonStr);
}
public void notifyConnectionClosed() {
synchronized(this) {
Iterator itr = connCloseListeners.iterator();
ConnectionClosedListener l = null;
while (itr.hasNext()) {
l = (ConnectionClosedListener)itr.next();
l.connectionClosed(this);
}
}
}
public void addConnectionClosedListener(ConnectionClosedListener l) {
synchronized(this) {
connCloseListeners.add(l);
}
}
public void removeConnectionClosedListener(ConnectionClosedListener l) {
synchronized(this) {
connCloseListeners.remove(l);
}
}
public abstract void logConnectionInfo(boolean closing);
public abstract String getRemoteConnectionString();
/**
* Gets the ConnectionUID for this connection.
*/
public ConnectionUID getConnectionUID() {
return this.conId;
}
public void setConnectionUID(ConnectionUID conId) {
this.conId = conId;
}
/**
* Sets the Service for this connection.
*/
protected void setService(Service id) {
this.service = id;
}
/**
* Gets the Service for this connection.
*/
public Service getService() {
return this.service;
}
/**
* Set what iMQ protocol version the client is using. This is
* determined when we get the HELLO message.
*/
public void setClientProtocolVersion(int version) {
clientProtocolVersion = version;
}
/**
* Get what iMQ protocol version the client is using
*/
public int getClientProtocolVersion() {
return clientProtocolVersion;
}
/**
* Gets the AccessController for this connection. An AccessController
* encapsulates a AuthenticationProtocolHandler and AccessControlContext.
* The later is obtained as result of authentication.
*/
public AccessController getAccessController() {
return accessController;
}
/**
* The term, principal, represents a name associated with the
* authenticated subject on this connection
*
* @exception BrokerException connection not authenticated
*/
public Principal getAuthenticatedName() throws BrokerException {
return accessController.getAuthenticatedName();
}
/**
* retrieves the connection state
* @return false if connection being destroyed
*/
public boolean setConnectionState(int state)
{
if (state >= STATE_DESTROYED) return true;
this.state = state;
return false;
}
public static String getConnectionStateString(int state) {
switch (state) {
case Connection.STATE_UNAVAILABLE:
return "UNAVAILABLE";
case Connection.STATE_CONNECTED:
return "CONNECTED";
case Connection.STATE_INITIALIZED:
return "INITIALIZED";
case Connection.STATE_AUTH_REQUESTED:
return "AUTHENTICATION REQUESTED";
case Connection.STATE_AUTH_RESPONSED:
return "AUTHENTICATION RESPONSED";
case Connection.STATE_AUTHENTICATED:
return "AUTHENTICATED";
case Connection.STATE_CLOSED:
return "CLOSED";
case Connection.STATE_CLEANED:
return "CLEANED";
case Connection.STATE_DESTROYING:
return "DESTROYING";
case Connection.STATE_DESTROYED:
return "DESTROYED";
}
return "UNKNOWN";
}
/**
* sets the connection state
* @return false if connection being destroyed
*/
public int getConnectionState() {
return state;
}
/**
* Place an object (by name) in the client data storage
* section of the Connection object
*/
public void addClientData(String name, Object data)
{
if (clientData == null)
clientData = new Hashtable();
clientData.put(name, data);
}
/**
* remove client data object (by name)
*/
public void removeClientData(String name) {
if (clientData == null) return;
clientData.remove(name);
}
/**
* retrieve client data object (by name)
*/
public Object getClientData(String name)
{
if (clientData == null) return null;
return clientData.get(name);
}
// dont sync .. we dont care who won
public void updateAccessTime(boolean received) {
lastAccess = System.currentTimeMillis();
if (received)
lastResponse=lastAccess;
}
public long getAccessTime() {
return lastAccess;
}
public long getLastResponseTime() {
return lastResponse;
}
public void checkClockSkew(long receivetime, long sendtime,
long expiretime)
{
// clockSkewCheck:
// -1 never check
// 0 check once
// > 1 check every X requests
if (clockSkewCheck < 0) {
// never check
return;
}
// if 0, just check once
if (clockSkewCheck == 0 && skewCheckCounter > 0)
return;
skewCheckCounter ++;
if (clockSkewCheck > 0 && (
skewCheckCounter%clockSkewCheck != 0)) {
// not the right iteration
return;
}
// OK, if we are here, we are checking for skew
long skewSecsMS = Math.abs(sendtime - receivetime);
long skewSecs = skewSecsMS/1000;
// OK, we also want to log something if the clock skew is
// less than our counter but we are immediately expiring the
// message
// So:
// - if we receive the message before we sent it
// (receivetime > sendtime)
// and the expiration time is small (< clockSkewTime)
// and the expiration is less than twice the
// skew time
// log something
long expirationMS = (expiretime == 0 ? 0
: ( (receivetime > sendtime )
? expiretime - sendtime : 0));
long expirationSecs = expirationMS/1000;
if (skewSecs > clockSkewTime) {
String msg = BrokerResources.W_CLOCK_SKEW_EARLY;
// we received the message before we sent
if (sendtime < receivetime) {
msg = BrokerResources.W_CLOCK_SKEW_LATE;
}
logger.log(Logger.WARNING, msg,
getRemoteConnectionString(), String.valueOf(clockSkewTime));
} else if (expirationMS != 0 && expirationSecs < clockSkewTime &&
expirationMS < 2*skewSecsMS) {
logger.log(Logger.WARNING, BrokerResources.W_CLOCK_SKEW_EXPIRING,
getRemoteConnectionString());
}
}
protected abstract void sayGoodbye(int reason, String reasonStr);
protected abstract void flushConnection(long timeout);
/**
* verify if the connection still exists
*/
protected abstract void checkConnection();
public abstract void cleanupMemory(boolean persistent);
public void debug(String prefix) {
}
public void sendConsumerInfo(int requestType,
DestinationUID duid,
int destType, int infoType,
boolean sendToWildcard) {
DestinationUID uid = null;
synchronized(consumerInfoRequests) {
Iterator itr = consumerInfoRequests.iterator();
while (itr.hasNext()) {
uid = (DestinationUID)itr.next();
if (!uid.isWildcard()) {
if (duid.equals(uid)) {
sendConsumerInfo(requestType, uid.getName(),
destType, infoType);
break;
}
continue;
}
if (duid.isWildcard() &&
uid.getName().equals(duid.getName()) &&
duid.isQueue() == uid.isQueue()) {
sendConsumerInfo(requestType, uid.getName(), destType, infoType);
break;
}
if (DestinationUID.match(duid, uid)) {
if (sendToWildcard) {
sendConsumerInfo(requestType, uid.getName(), destType, infoType);
} else {
Globals.getConnectionManager().getConsumerInfoNotifyManager().
consumerInfoRequested(this, uid, destType, infoType);
}
}
}
}
}
protected abstract void sendConsumerInfo(int requestType, String destName,
int destType, int infoType);
public void addConsumerInfoRequest(DestinationUID duid) {
consumerInfoRequests.add(duid);
}
public void removeConsumerInfoRequest(DestinationUID duid) {
consumerInfoRequests.remove(duid);
}
}